Role Based Auth in NodeJS with ExpressJS and Auth0

Tyler Clark
InstructorTyler Clark

Share this video with your friends

Send Tweet
Published 2 years ago
Updated 2 years ago

Role-Based Access Control (RBAC) is the idea of grouping permissions together by a role. Users are assigned a role through the Auth0 dashboard the corresponding permissions are placed on the user's access token (JWT) after authenticating. That access token can be extracted by an Auth0 SDK (this video uses JavaScipt/ ReactJS) and sent to a custom server.

Server Side logic (this video is based on NodeJS and ExpressJS), can then check for specific permissions and then allow or reject certain resources.


Starting point- https://github.com/twclark0/roles-scopes-node-auth0/tree/roles-beginning

Ending point- https://github.com/twclark0/roles-scopes-node-auth0/tree/roles-final

Tyler Clark: [0:00] Before we get too far, this video assumes you have two things already set up within your app. The first is authentication, which means that you can log in and log out, as you can see here using Auth0.

[0:14] What is role-based access control? Well, RBAC, as it's commonly called, is the idea of limiting resources to users based on their role and what permissions are assigned to those roles.

[0:27] Think of an app that has admins, developers, and regular users logging in. Ideally, you'll want to limit certain pages, data, and functionality depending on their role, and again, more specifically, what permissions you've designed for their individual roles.

[0:43] With that, let's take a look at our code and the API we've registered with Auth0. Our code here currently has two buttons, they're hitting two Express.js endpoints. One is public, meaning it can be hit by anyone, and the other is protected, which means that there needs to be an authenticated user within our app.

[1:01] Our goal is to add another button to sit alongside these two here, and call a third endpoint that is checking for certain permissions that have been assigned to a particular role.

[1:12] This is our Express code here. As you can see, we just have the two endpoints, and we want to make that third. Here is the public and the protected. Notice that the protected is using a couple npm packages to verify that access token that it gets. It's wrapped up in middleware and used here.

[1:34] Now, let's head over to our API instead of our Auth0 dashboard, and check out the API. I've called it Quickstart API, and under the Settings tab, what you see here are all the default settings.

[1:46] I've also added a couple of permissions to this API, read messages and read appointments. Both of these will come in handy as we define roles and permissions here later on.

[1:57] Don't close this tab, but we're going to head back to our code and start coding. Here is the React code that holds the button JSX and the fetch calls. There is a lot going on in this page, so don't worry. We're going to focus just on adding this third button and setting up its onClick handler.

[2:13] Let's copy this other button and update the function name and button text. I'll do the same thing up here, copy the protected endpoint and update the endpoint address.

[2:24] Notice that nothing is different functionality-wise between these two. That's because the access token provides the information needed to determine which permissions the authenticated user has, based on their role. Perfect, now our new button lives in our UI.

[2:43] Let's go ahead and create this endpoint. Again, I'm going to start off by copying the protected endpoint's code, update the route and the return text. Perfect.

[2:55] This endpoint is checking for a valid access token, though, we need to do one more step, we need to check permissions on that access token. In order to do that, we'll need to install an npm package, the express-jwt-authz package.

[3:11] This package is how we will check for permissions, which is sometimes also referred to as scopes. This package is created by the Auth0 team, and we'll review some of these options here in a moment.

[3:24] Once that's done installing, let's require it at the top of our page. We'll call it jwtAuthz, and then we're going to be creating a middleware similar to the authorize access token middleware that's used in both of our protected and role-based endpoints.

[3:40] I'm going to call it check permissions because that's what it's doing, and for now, we're going to look for the read messages permission passing through a custom scope key of permissions. We'll pass it along to our middleware for the role-based endpoint, and that should be it.

[3:55] With our backend and frontend code ready to go, now we move to creating roles and configuring permissions. First, I want to head over to the UI and see what happens when we ping our role-based endpoint as it is.

[4:08] Notice that we get an error here on our request, and it's saying we have an insufficient scope. This is expected because we haven't created a role that holds the read messages permission. We haven't assigned that to a user.

[4:24] On the Headers tab, if we go to our authorization header, copy the access token here. Go to jwt.io and paste that access token here, we can see what's coming on it. As you can imagine, we are not getting any permission on the token, which is why we're getting that error.

[4:44] Back in our Auth0 dashboard, on the left, let's go to User Management and click on Roles. Here, I'm going to create a new role, give it a name of admin, a description, and then click Create.

[4:58] Remember, a role is just a special way of grouping permissions together. On my Permissions tab, let's add a permission to this role. I'm working with the Quickstart API, and for right now, we're wanting to assign the read messages permission only. Notice this warning here at the top, "This API does not have the RBAC enabled."

[5:18] Once we save this, let's head over to the API and enable that feature. This is an important step in adding permissions to our users access tokens for verification.

[5:29] Add this permission, and then we're going to assign this role to my user. In Users here, I'm going to Add Users and look for my user, which is just tyler@Gmail.com. Perfect.

[5:44] Let's not forget that warning. We'll go back to our API underneath Applications, the Quickstart API, go to Settings and look for the Enable RBAC here. I'm also going to do the Add Permissions in the Access Token. This setting will add our users permissions based off their role to the access token with a permissions property.

[6:05] This is important because, remember in our Express code, we specifically stated that our permissions would live on the Permissions Property in the Access Token.

[6:16] Let's not forget to save our settings. Once that's done, head back over to our UI. In order to get a new updated access token with my new role permissions, I need to log out and log back in again.

[6:31] Once this is done, let's hit the endpoint and see what happens. Looks like it's a success. Our user now has the correct permission granted to them because of their role as an admin, so the endpoint returned a success message.

[6:45] Let's see what our access token looks like in jwt.io. Grab our access token, copy it and paste it back into jwt.io. Now, notice two things, our access token has this new permissions property because of that permission setting we set up in our Auth0 API dashboard settings, and because of this user's admin role, they're receiving the read messages permission.

[7:15] Before we wrap this video up, I want to touch on some of the other config options within the express-jwt-authz Auth0 package. We use this custom scope key option to specify the permissions property as the location of the user permissions.

[7:29] As you can see, the default is Scope. If we were to remove that from our code, it'll fail. Scopes is how third-party applications can access resources on a user's behalf. A scope goes hand in hand with permissions, with the caveat that it's the process of delegating permissions to those third-party apps.

[7:50] CustomUserKey default is user, which is where our permissions live currently, though this gives you the power to change that depending on how you want to work with your access token.

[8:02] FailWithError does as it says here, will pass the error along the middleware chain instead of feeling the call as is, and checkAllScopes, make sure that all the scopes listed are in the user's access token, not just one to pass.

[8:17] Let's take a closer look at this. In the code, let's add that second API permission read appointments to this array. Remember, the admin role currently only has the read messages permission. Though, if I try hitting this endpoint with the changes, notice that it succeeds. Again, this is even though my user only has one of the permissions, the read messages.

[8:42] If I want to enforce that the users have access to all of the permissions I define and it's going to work, then I need to add checkAllScopes is true. With that in there, now when we try it, you're going to see that we're going to get an error here.