Next.js Role-Based User Authorization & Access Control | Next Auth Protected Routes

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] hello and welcome I'm Dave today we're going to learn how to use next authjs to add role-based access control and user authorization to your next js13 application and I'll provide links to all resources in the description below I'll also provide a link for you to join my Discord server where you can discuss web development with other students and you can ask questions that I can answer and receive help from other viewers too I look forward to seeing you there as I expected after last week's next auth tutorial many have requested to see how role-based access control and user authorization can be applied to protected routes in next JS so we're going to start with the completed code from last week's next auth tutorial and I'll link to the starter code in the description below and also I'll link to the video because if you haven't completed that next auth tutorial you really need to do that first before you try to apply anything that you see in today's tutorial I've got vs code open and with that starter source code that I've talked about and is linked in the description just note that I'm using next JS 13.4.7 I believe 13.4.8 may have been released yesterday or today and of course if you're watching this in the future there may be a newer version but if you want to follow along with the exact version that I have you can go ahead and install next 13.4.7 just like I'm using today just wanted to note that okay let's move on and in the source code last time we set up a route Handler inside of app API and it's in auth and then in this catch all route that is next auth and what we need inside of here is the options file where we set up our next options we need to make some changes here today first and the first thing I want to do is import the GitHub profile so I'm going to import this is GitHub profile from next Dash auth providers then slash GitHub after that we're going to use that inside of our providers for our GitHub provider because we need to add a profile function at the very top here of our GitHub provider we'll just start with our profile function and it receives a profile and that would be the GitHub profile type that we just imported now after that we'll go ahead and take care of the body of the function one thing I want to do is just console.log a profile right here so you can see everything that would come in from GitHub if you run this and I'm going to comment this out but I want to leave it in the source code for you just so you could check to see everything you're receiving from GitHub now this would log to your server console so it would be in the terminal but after that what we really need in the function is to return whatever we want to return from the profile so we can spread right in the existing profile and then we can add a role to that now GitHub or any oauth provider may not be providing the role we expect to get so if we want to assign a role from them that could be inside of the GitHub profile for example we could say profile dot roll but then we can also use a null coleskine operator here to just say if not provide user and that's more than likely what it would be also we could just assign the role of user anytime anybody logs in with any oauth provider like we're doing here so you might not have anything for that specific role that would be coming in anyway I just wanted to show the example of how you could get it if you did after this we're also going to say there is an ID now there's already an ID from GitHub but the GitHub profile has it as a number and we've been using it as a string inside of our credentials provider as we can see down here with this fake user that we created in the last tutorial so I've got ID 42 and it's a string so I need this to be a string as well to match the user that we have or the user type as well so I'm going to say profile dot ID which is what we get from GitHub and then I'm going to say to string just so it matches and we don't get a complaint from typescript that way also we were previously using an assigned image but now that we start using this profile function it doesn't really come in as the image if you check what you get from GitHub it comes in as an avatar underscore URL so we just kind of need to rename that to the image here and it's being sent back now we could specify like there's also an email that we were using or potentially using we could specify that and not spread in the rest of the profile if we didn't want to you can only take the things you want or that would match your user type actually and that might be a better way to do it but this is the way you would get everything from the profile as well so that's what I'm doing and then we're just adding in the additional properties that we need to that user right here now all I need to do here is add a comma and we're finished adding the profile function now this would work the same way with any oauth provider we only provided the GitHub oauth provider to this example application just so it shows how you could do this with Google or Facebook or anyplace but they may not provide the same profile properties and values so you would just have to look at that and you could do that by logging that profile to the console to see what you were receiving from the that different service because each one could be different and now we need to scroll down into our credentials provider and this is where we were logging in with a username and password the only thing we need to change here is really the fake data that we're using because right now it has an ID name and a password we also need to add a role for this user and I'm going to set it to admin for our first example so this would be and as this note says that I left in the last repository this is where you could retrieve user data to verify try credentials wherever you were keeping that user table or user data so I'm just hard coding one in and we can use this to check our examples with but you could retrieve it right here as well that's the only change inside of the authorized function here for the credentials provider so we've got one oauth provider at the top here with GitHub and our credentials provider which would be a username and password I'm back in the browser at the next auth docs and this is the subtitle here persisting the role that we're at and so what we need to look at is how we might want to do that and really the focus here is the callbacks part and we don't have that yet in our options and we need to add this to our options file so we see the providers up here it comes right underneath the providers and this is how we will persist the user roles now the JWT is how it would persist on the server we only need the session if we're going to do that with a client component I'm going to show both so we'll go ahead and add both today back in vs code I'm going to scroll for a little more room this goes under our providers and it is called all backs this will be an object and let me go ahead and paste in that reference URL just so you'll have it in here for where we were looking at that information in the docs so I'll put that reference URL in here after that we can put in our async JWT function is going to receive the token and the user now inside this function I'm going to say if we have a user then the token dot roll equals the user.roll and then we're going to return the token now don't worry about typescript's complaints we will fix that in just a little bit let's go ahead and add the other async function we need and I should add a note here as well so this is if you want to use the role in client components so I'll put that right here and now if we want to use that role we need our async session function and it's going to receive session and the token now inside this function let's say if there is a session user then we're going to say session Dot user.roll equals the token dot roll that we will have and then we'll return this session now currently typescript isn't too happy with our user that has a role or our session user that has a role so what we need to do is module augmentation we can actually extend these current interfaces as they exist to go ahead and add what we need and to do this I'm going to come over here into the file tree just in the same level where we have the package Json you could create a folder called types if you want to I'm just going to create it at this level right here and this is going to be a file named next dash auth dot d dot TS now again as I'm trying to provide references for you you can reference the docs at this URL where they do talk about module augmentation and that's what we're going to do here today so we're going to import the default session and also the default user and then after we import those from next Dash auth we also need to import JWT and the default JWT those are going to come from next Dash auth slash JWT now we start with a declare module and this is going to be next Dash auth and then inside of this object we have an interface that is the session now for this session inside we have a user so that is our session.user and then here we need to go ahead and Define an ID that is a string and a role that is a string and then after that to include the rest of what was normally with the session we need to say and default session so you can see how we've created or extended the session actually we've added the extra part of the user and then what was already there for the default session now let's do this for the user interface but it's just a little different we'll say interface user and now we need to say extends default user and this is because we don't have a nested user object inside that we're extending we're just extending user right here and all we need to add is roll string and now in a separate module so I need to scroll up we're going to go ahead and declare once again so declare module and this will be next Dash auth slash JWT and inside of this will have an interface that is the JWT and we're extending the fault JWT interface and we'll once again add a role that is a string so now all of those changes should be saved and applied if you're interested more about module augmentation and how you can extend these types go ahead and look at this URL it doesn't play a specific role into what we're doing today other than we need it for adding roles essentially and here we added a string ID as well but this is what we need to go ahead and augment the types that we're using today so now back in our options we no longer have these typescript red squiggly errors that we had for adding the role to the session.user or the role to the user here inside of these functions and now because we have changed the user specifically here we also need to look at our component that was using the user type before which is the user card I believe yes and I was defining it here we can go ahead and remove this and of course now we'll get some complaining until we Define the user once again so let's import type user this is going to come from next auth because we're using the type that we extended and so now we've extended this user and we can just use it right here without that previous definition so you can change that one other thing I want to add in the code is to go ahead and display the role on the page when we get it and I guess yes I can just scroll down and put it here at the bottom after we have the page type so I'm going to shift alt and the down arrow to just copy that line down the paragraph that has the page type and I'm going to display the user dot role right here and I'll just remove page exclamation mark and I'll go ahead and add roll and a colon there just so we can display the role when we view this page no matter what hey it's any specific page because this card the user card could be used on any page so this could change depending on which page we're going to view and with these changes in place we now need to go to the middleware to make some final changes before we can test out the application once again now we were previously applying the authorization or the authentication I should say in the last tutorial for the extra and we don't have a dashboard page but I gave this as an example but we were applying this with the middleware as one of three different examples of how we could apply that authentication but right now we're going to remove the way we previously did this so you could comment it out to keep this in your code if you want but we no longer need this export default here so I'm just going to delete lines one through three all together I am going to paste in an other reference I'll alt Z to wrap that down this is under Advanced usage in the next JS auth configuration here so this link will take you to what I'm going to be referring to here because we're going to import a with auth wrapper and this is going to wrap around our middleware function so it comes from next Dash auth slash middleware now that we've imported that we need to actually import one more thing this is import type this will be the next request with auth type that we also get from next auth middleware so I guess they could come in the same import actually here we could just double click and I will cut and paste because they're coming from the same place we'll just put them in the same import statement but we need both of these as we apply this new strategy so I'm going to start with export default with auth and then have a parenthesis and now this with auth augments your request that you'll get in the middleware function and it puts the user's token in that request object so now we'll have a function middleware and now we'll have a request and let's just make this the next request with auth type now inside of this middleware function we can do several things but we're not going to do much right now but let's go ahead and put a couple of console log statements so you can look at the request dot next URL Dot pathname and this would be whatever path name is being requested we could also log the request dot next auth dot token if we want to see what the token looks like so we can do several things in the middleware and we will come back to that let me go ahead and paste in the comment that I was telling you as well to let you know what this with auth function does or this wrapper actually it augments your request with the user's token so you want to remember that you now have the token available on the request it's at request.nextauth dot token but now after this middleware function we need to provide callbacks and this is where it becomes fairly powerful so here we'll just have callbacks and we're going to have an authorized function this authorized function need a colon there actually is going to have the token passed in and now we could compare the token so here's just one example we will change this but in this example we'll just look at the token and we'll look at that token role see if it is equal to admin and I was wondering why I had so many red squigglies and I thought it was because I hadn't finished typing but no actually callbacks goes inside of an object here so we'll start with an object and we'll end with an object we'll save we'll get the proper formatting you could put a comma after the object that is assigned to callbacks as well what's important to understand here is unless our callback our authorized function inside of the Callback unless it returns true so unless the token roll equals admin this middleware function will not execute so it only executes if authorized returns true now this applies to everything we would have inside the matcher so at this point if we have the role admin in our token then we can go ahead and view the extra page for example and if we had a dashboard page we could view it but if we don't we won't be able to view the extra page we will not be authorized let's save our changes to the middleware file but before we can test this out one thing I would like to remind you is if you've cloned the repository from the previous tutorial you still need the environment variables I never include those as you shouldn't include environment variables in GitHub and we created those in that first tutorial so if you haven't completed that first tutorial that would definitely help you I'm going to go ahead and create the dot env.local file now here at the same level as the package.json file so it's Dot env.local and then inside of this I'm going to delete my values after this tutorial you can't use mine but what you will need to do is learn how to create a next auth secret which is in the previous tutorial also we created a GitHub ID and GitHub secret for oauth with GitHub in that tutorial so you need values and once again you can't just copy mine that won't work so we'll save this if you don't have these values from the previous tutorial please go complete that then you can come back and finish the rest of this with us and it is linked in the description but now that we have our changes and we've made sure that we have the environment variable values that we need let's go ahead and start our application so I'm going to do control in the back tick and now I've got a terminal window open I'll type npm run Dev to start the application should get started on localhost 3000 I'll control click that and let's take a look at the application in Chrome here when it gets started and I'm currently logged in let me go ahead and sign out so we can start from scratch and we should see you shall not pass so that is the home page that we were left with it was checking for authentication in one way and we checked in different ways for the server and client and extra as well that was in the previous tutorial but now we want to go ahead and sign in so I'll sign in with GitHub and once I'm signed in we should see a welcome here I'm seeing my GitHub image a home page and it says my role is user and that's what's expected when we go ahead and sign in with GitHub now I should be able to see the server page as well and now it says it's a server page I should be able to see the client page and I can and once again still have the role of user I should not be able to see the extra page and now it takes me to the sign in but I'm already signed in so this is something we'll want to fix here as we move forward but it is working it did apply that authentication to the extra page in the middleware as expected so we just couldn't get there and by adding the user.roll to that user card it looks like like I've created an error at least at some point along the way it didn't always error but if we don't have a user role we do get an error on that component no matter which other component it is nested in so let's pull that back up as well and we can put in a check for that back in vs code I'll close the terminal I think we're having a problem with the client page if we go to this page it's the only one that's not really checking here before we get the user and yes typescript is showing us an issue here as well so let's just do something like say if we have no session user and let's just return and we could return something like no user or anything else we wanted to but essentially we just don't have anything so I'm putting in that return there and you could put whatever you want in that spot I'm going to actually fix this more over so it shouldn't ever get here as we go back to the middleware but typescript may still want to see something like this in here just so it doesn't get to this part and not have a user now let's once again open a terminal and of course because of the error and not everything is running let's go ahead and Ctrl C to stop once again start the server and what we want to do now is log in as an admin and just make sure the extra route is working as expected when we are an admin as well so I'll pull this back up I'm going to sign out and we'll want to use the username and password now and I had hard-coded mine in so let's put this in and if you're looking for where that is hard coded that is in the options is where we had that instead of pulling it from a database and yes now we can see the extra page because we are in admin when we log in that way and we can check that by going to any of these other components that displays the role so the server component client component both show that we go to home it also displays which role we have because they're all using that user card back in vs code let's go back to the middleware and we can fix some of these things that aren't quite so smooth right now let's talk about the good and the bad of what we currently have too the good is that admins can access the protected routes in the matcher here is our matcher for the middleware and we've currently got the extra route in here and the dashboard route which really doesn't exist but the extra is being protected and only an admin can view that extra route so we've protected the route but it's more like an on off switch and so that's kind of the bad thing because it doesn't give the tiered structure we need to apply this to many different roles for example so we could make some changes and do that imagine we have a user role we have a manager role and we have an admin role and we want to provide some structure for each of those I want to make just a few changes to the application to display this more I guess in a better way I should say before we come back and make all the changes to the middleware so first let's just go to the default page because we were of course looking at this to see if we had a session and then displaying the user card or you shall not pass message but I'd rather just go ahead and delete all of that and put in an H1 and I want to give it a class of text Dash 5xl from Tailwind now inside of this I'm just going to say this is a public home page so this is going to be displayed even if we aren't logged in so that will give us one example of not being logged in and one page we can see so I can remove that session there and we shouldn't need any of the Imports at the top anymore either this becomes a very very simple component now if we look at this server page we're not going to make any changes here but we just need to be logged in to see this it's not picky about what type of login so a user a manager or an admin can view the server page so we'll just leave this as is we definitely have to be logged in as it checks that right there but we don't have to have any specific role to see it after we're logged in and now let's look at the client page now for the client page we were making sure we have a session before we attempt to pass it to the user card and then we just are returning nothing here we could return null or we could return a message if we wanted to in an H1 anything like that but what we could do and I'm just going to copy and put this in because I'm not going to leave it but I do want to go ahead and show you what you could do I want to put in something like this and I can go ahead and comment out that line we could check the user role right here and this would allow us to do this on a component or page basis if you will so we have session dot user.roll we're checking to see if it is an admin or if it's I'm sorry if it's not an admin and it's not a manager so it needs to be one of those two to view what we would go ahead and display and if not it returns access denied but if we're an admin or we're a manager then we could see what's on the page and of course this takes the place of this simple statement right here because it's already checking to make sure we have a session.user.roll otherwise it's returning access denied so this also makes typescript happy but I'm going to show how we can do this with middleware without needing to do this in every component or every subtree or however we would apply this we can just handle it with middleware so in that case I'll comment this out but I then once again to make typescript happy I'm going to need to put this back although this shouldn't be used if we apply the middleware correctly okay going to the middleware file the first thing we're going to change is this callback right now we're using the authorized function as an on off switch so we return true if we're an admin and false if we're not and so that really limits what we can do it's basically an on off for the admins only instead let's go ahead and just make sure we have a token here and confirm that token so to do that we can delete everything after token but let's put the double bang operator in front of it just to verify that it is absolutely true that we do have a token so if we have a token this is true and now the middleware function will execute and that's what we want to check out here next so with that middleware function we can start checking more individual things inside of here so let's first say if and then we'll have request dot next URL dot pathname dot starts with and now inside of this let's put in our slash extra route that we're going to have and now after that I'm going to bring this down to another line just pour some room we'll do the double Ampersand so if it starts with extra and request dot next auth dot token dot roll is not equal to admin because actually you need to be an admin to see this extra route so we're saying if it's not equal to admin but yet the extra route is being requested then we're going to return a next response dot rewrite and that means the slash extra route will still display in the URL bar when we use a rewrite and we can say new URL and we're going to create a new page and this is going to be the denied page that we can use and then after denied here let me give the double quote then we're going to just pass in the request URL because that's what URL accepts here so we'll pass in the original URL but we're sending them to denied that the original one will be displayed and I think I need to import that next response up at the top yes we don't have that yet so let's go ahead and do that that will be import next response from next server that looks correct so we're good there yes our red squiggly is gone so this would apply to the extra route now let's go ahead and do one more and to do one more we want to apply it to the client route so we need to add the client route to our matcher as well so I'll put that here client a comma after and now we have extra client and dashboard in our matcher now we can come back up here and add our if statement or the client route as well so once again if request dot next URL Dot pathname starts with now we'll put in slash client and let's break this down too because we're going to then have and request dot next auth dot token roll and then not equals admin and then we'll also have another double Ampersand here and I'm just going to copy all of this because we don't need to change much of it control C control V to paste it in now I'll be putting manager here as well so if it's not equal to that then we'll have our return with the next response that I can copy and paste right here as well because once again it should go to the denied with the request URL being displayed so this made the client route now available to an admin and a manager either one so as long as we're an admin or a manager we should be able to view the client route but if we're not an admin or manager and we request the client route we are going to get redirected essentially but with a rewrite which is important because it will display the requested URL but it will send us to the denied route so let's save these changes in our middleware and let's make that denied route now quickly do this I'll just click the root page here and I want to create a new directory and it's denied and inside of this denied directory I'm creating a page.tsx and inside of the page.tsx I'm quickly pasting in the page that I am providing but you could create your own essentially I'm importing link so we can link back to the home page but I just want to display a message here that says access denied it lets them know they are logged in but they do not have the required access level to view this page and I can say this page because it's going to display the route they requested at the top whether that's slash extra or slash client okay with all of these changes in place let's open a terminal it looks like our application is still running I hope everything's applied if not we may need to restart it you never know for sure but I'll go ahead and check now and we are on a public home page and that's what I wanted I'm not sure what I'm signed in as so I'm going to check the server right here it says I'm currently an admin that's okay I should be able to view everything this way so let's verify that here is the client page the home page is a public page now so now let's check extra and yes I can view extras so go back to home sign out and notice I don't have a callback if you can see this up here so I shouldn't be sent to one of the other Pages hopefully let's sign out now it took me to a login and maybe this is cached I did notice it gave me the callback for the extra page here and I don't want that so I'm just going to delete it for our example I'm just going to go to the home page now and now once again try to sign in and when I sign in now I'm just going to sign in as a GitHub user and once I'm signed in there it took me to the extra page so it definitely wants to take me to that extra page after sign in this could be something I need to clear my cache over you could comment and let me know if you have the same problem or you figure out what's going on with that but I get the access denied because I am just at the user level and I shouldn't be able to view the client either let's view the server because we're logged in no matter what role we're logged in we should be able to view the server page let me go to the client page access is denied that's exactly what we expected it does say slash client up here when I go to extra access is denied but it says slash extra up here as well that's all working as expected that's what I want let me go ahead and sign out once again and I want to go back to the home page I'll delete all of the extra stuff there I'm going to go back to vs code though and what I want to do in vs code is go back to the options and since we're not pulling in users from a user table I just need to change change what I've hard coded here and I had admin let me change this to manager and we'll save that as a manager role and now I should be able to see the client page but I should not be able to see the extra page so I'll bring this back and I hope I don't need to restart the app for that too happen I'll refresh at least see the server page and it says I need to log in okay maybe I well I did log out that's right so now I'll log in with my password but I should be a manager yes there's role manager so that's good let me see if I can see the client page I can see the client page as expected and remember this is being applied through middleware so I couldn't see the client page as a user but I can as a manager but only an admin should be able to see extra and that's what we've got so my manager cannot see the extra page but my admin could okay even the documentation said some of this was Advanced and I know it was a lot to follow but I hope it has helped you out and in this tutorial I hope you've learned that you can support multiple user roles and role-based access levels in next JS with next auth JS the next auth is very customizable and if you have more requests please let me know in the comments remember to keep striving for Progress over Perfection and a little progress every day will go a very long way please give this video a like if it's helped you and thank you for watching and subscribing you're helping my channel grow have a great day and let's write more code together very soon
Info
Channel: Dave Gray
Views: 54,183
Rating: undefined out of 5
Keywords: next.js role-based user authorization, next.js, nextjs, role-based user auth, role-based user authorization, user auth, role-based access control, rbac, next auth, nextauth, nextauth.js, protected routes, nextauth.js protected routes, nextauth protected routes, next auth protected routes, nextauth rbac, next.js rbac, nextauth user authorization, next auth user, next auth user authorization, next auth user roles, user roles, next auth middleware, next.js protected routes, auth, next
Id: ay-atEUGIc4
Channel Id: undefined
Length: 36min 21sec (2181 seconds)
Published: Fri Jul 07 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.