Playing Next Lesson In
Transcript
-
Now, at the end of the day, there's some things that we want our web authenticated users to be able to do within our web dashboard
-
that we don't want our API authenticated users to be able to do. For example, within our organization settings, we're not going to want our API authenticated users
-
to be able to manage or invite users inside of the organization itself. We want that restricted to just this web dashboard. Since that is an admin specific functionality,
-
but our access tokens are going to be for the organization itself that doesn't have one of those user-based roles. So to easily keep the separation of concerns
-
between those different routes for our web application and then our API application, what we can do is jump into our start. And then inside of this application,
-
I have a route split between a couple of different files here between our auth and currently our web. And then we'll add an additional one here for our API as well. So if we take a look at our web routes,
-
for example, we have our authentication middleware being used as a group against a number of different routes. As we have it here,
-
all that this cares about is that something is authenticated. In this case, that could be our user or it could be our organization through our API access tokens.
-
So in order to keep that separation of concerns that we had talked about, we want to limit this auth middleware to specifically check just our web guard for our authentication. So we can pass some options into this.
-
And one of these options is guards. And this accepts in an array of guards that we want this authentication to check against. So we'll specify web in there.
-
And now this will only authorize access to these routes if a user's authenticated specifically with the web authentication guard. If an API user tries to reach one of these routes,
-
they're gonna get an access denied error or redirected away depending on content negotiation. So we also have that being applied down here at the bottom for the main set of these web routes as well right here.
-
So we wanna do that here too. So we'll add guards and specify that this should only accept in the web guard. Our auth routes are mostly going to just be checking
-
whether or not the user's a guest, which is a non-authenticated user. So we don't need to specify it on there, but we do have this one here for our logout
-
that we could add a guard specificity on as well. So we'll add guards here and specify that that one should only be applicable to web. Since the way that we're going to be using these access tokens,
-
there's not really going to be a logout mechanism. So with those set, we now have a separation of concerns between our routes and which auth guard they should actually use
-
and authorize access to for those routes. Then we can go ahead and add a new route in for these API routes that we'll be adding. So we'll do api.ts,
-
and let's go ahead and give ourselves a router. Be sure to import the router from AdonisJS Core Services router, not the one from Inertia, and get ourselves a new group going on here.
-
Inside of this group is where we will define our API routes. We don't have any defined at this moment, so we'll just add that comment in there, and then we'll have this use a couple of middleware.
-
So first and foremost, we want this to use our auth middleware. So we'll call auth, and we want this to specifically check the API guard
-
for that, limiting web authenticated users from these routes. Now, in addition to that, there's also a middleware that pre-fills our organization onto the HTTP context.
-
We'll keep that middleware intact, but we do need to alter it a little bit to work correctly with our API access tokens. So we'll alter that here in a little bit, but there's actually a middleware
-
that we want to create as well that will force the content negotiation for our API routes to be application JSON.
-
So let's do node ace make middleware within our terminal here, and we'll call this force JSON response, and we'll have this be a named middleware
-
as we don't want it applied to our web routes. All right, we can clear our terminal out there, jump back into our text editor, and let's go ahead and add that into this route group as well.
-
So middleware.forceJSONResponse. I'm gonna go ahead and put it first here so that the response is already in place for our auth guard as well as our organization guard
-
in case anything should either error out or come back as unauthorized in either one of those. Now we have a checklist currently, have a couple of things that we need to do. We need to update our organization middleware
-
to handle our API. We haven't yet filled out our force JSON response middleware, and we also need to register this new API route file
-
within our applications preloads. So we'll do that in the inverse order that I just listed. Let's go ahead and start by jumping down into our adonisrc.ts file.
-
This is where we add in the preloads that we want to be loaded in during the boot cycle of our application. You can see that we already have our other two route files defined here.
-
Had I jumped into the ACLI and run node ace make preload, this would have automatically been registered for me, but since I just right-clicked the file tree and created a file, we need to manually add it in here.
-
So it's gonna be start routes/api, just like so. And now our API file will be loaded along with our routes whenever our application boots up.
-
Okay, let's close that out. And next, let's go ahead and fill out our force JSON response middleware. So let's go up to our middleware here, where we have our force JSON response middleware.
-
We can go ahead and just get rid of everything that's in here so far. And then from our HTTP context, let's go ahead and extract out the request. And what we wanna do with this middleware
-
is exactly as the name suggests, force the response for the content negotiation of the request to be application JSON. So to do that, let's go ahead and grab the current headers
-
from the request. So request.headers. This is gonna give us back an object of type incoming HTTP headers. Content negotiation goes off of the accept header.
-
So if we overwrite this and force it to accept application JSON, that's gonna force the response type via content negotiation for this request to be application JSON,
-
fulfilling what we're after here for this middleware. Once we have that set, we can go ahead and continue onward with the middleware tree by returning next. Next is the function that when called
-
allows AdonisJS to move on from this middleware to the next one or to the controller as a whole. All right, we can close that one out now as well.
-
And then that leaves us with our organization middleware that we need to update. So let's jump into there next. We need to update this to handle our API. Everything in place here so far
-
is specific to our web application. It's going to get the active organization off of the user's cookies, pre-fill the user's role inside of that organization,
-
and then add that onto the HTTP context along with their abilities based off of the role. Then it will add it into Inertia's data to share with our front-end application.
-
So we can bypass all of that if we are inside of an API request. And with how we have our authentication separation of concerns,
-
it's actually really easy to discern whether or not we're in an API request for this. If we have an API authenticated user, then we want to bypass all of this
-
and instead use the authenticated organization as the organization that we populate onto the HTTP context.
-
Otherwise, we can assume that it's a web authenticated user and have this middleware remain as is. So at the start of this handle method,
-
we'll just do an if ctx auth use API.user.
-
So if our API guard has an authenticated user bound to it, then we know that we have a user authenticated via our API.
-
So we can do ctx.organization equals ctx auth use API.user and assert that that user's there.
-
Since we're specifying that we're using the API guard, our user is going to be of type, ah, where is it here? Organization, and then it's also going to bind
-
in that current access token that that organization has. And that type is as defined inside of our API guard within our auth configuration. So despite this saying user,
-
this is actually our organization authenticated with our API. Once we've populated our HTTP context's organization property,
-
we can go ahead and continue onward with the middleware tree by returning the next function. That will bypass everything else down here since we've now returned,
-
and next will allow the middleware tree to continue onward from this organization middleware. If we don't have an API authenticated user,
-
then it will continue onward with this middleware as is usual. So that is all now a-okay and ready to go. If you're not coming from our building with AdonisJS and Inertia series,
-
what this is doing is adding the organization onto our HTTP context. Further down on this page, we have the type augmentation for that, where we're extending the HTTP context
-
and adding on the organization right here. Oh, looks like we also have an organization ID there, which we are, yep, we're populating right there. So we can go ahead and populate that for our API as well.
-
CTX organization ID equals, and we just do CTX organization.ID to simplify the reach for the organization there.
-
And then throughout our controllers, so if I scroll on up here, go into our controllers, and let's go into, say, our courses controller, we're then able to extract the organization
-
directly out of our HTTP context to use as needed. So that's what we're doing inside of this middleware, just grabbing the requests organization
-
and pre-populating that onto the HTTP context.
Separation of API & Web Auth Guard Concerns
We'll restrict our routes to their applicable authentication guard. Ensuring our web routes can properly authorize using their role-based authorization and our API can properly authorize using our access token abilities.

- Created by
- @tomgobich
- Published
Join the Discussion 0 comments
Be the first to comment!