Set up Next-Auth with Next.js and Prisma with this ultimate guide!

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
today we're going to go through and set up off JS with next js13 in the new app directory using the brand new API routes we're doing this with a beta beta version of the authjs library now off JS previously was known as next auth and the libraries right now are basically interchangeable so while I go ahead and install the next auth package if you're looking at this in the future you might be using the off JS package by then the content and everything in this video should remain pretty relevant for a while because we're doing everything so forward looking we're using the credential provider for this authentication so that's going to require a username or email and a password and we're going to look up the user and validate it against our database I'm going to be using postgres but because we're using Prisma feel free to use whatever database you want to use lastly once we have authenticated the user we're going to go through and make sure you know how to look up the session information even change some of the session information if you need to as well as protect any routes you have throughout the application with that let's get started I'm gonna pick up basically where my Prisma tutorial left off we already have Prisma installed and configured and there's a very basic user model we have here I'm going to be using email as the unique login that we're going to use but feel free to use username or something else if you're doing this on your own also we're totally in the app directory so here I've cleaned up most of the code and there's a very basic page that has no Authentication and the layout is exactly what you would expect for the root layout all right let's go ahead and add some Authentication the first thing you need to do is you need to install next auth now I'm installing a very specific build from a pull request which is adding in the functionality we need but this should be added to a beta or stable release sometime soon once it's installed we can jump back to the application and we can make the API route that next off is going to use remember we're doing this totally in the app directory using the brand new API routes so that's why we have to be on The Cutting Edge of the package version the file that we're creating is going to be API in the off folder with a catch all next off Route so go ahead and create that now this doesn't have to be in the API directory that's kind of a holdover from before but it makes the migration from the Pages directory very easy now we have to fill in the basic setup for this route I've dropped in some of the code but it's pretty self-explanatory we're importing the credential provider which is what we're going to be using to do the validation and then we're exporting auth options and making sure it's typed correctly so we can get all the benefits of typescript here's our credential provider and it just requires a basic name which is going to show on the login page and then what credentials you're kind of looking for so we have an email which is a label email and it has a placeholder and what type the form input should actually be and then here we have the password that we're accepting as well and for the authorization we're simply mocking the authorization for right now it's very important to iteratively test as you're going and this will let us say okay if we had a user and returned it what does that look like in terms of a full authentication flow here it's a little different we create the Handler with next auth and then we have to export the Handler as a git and post function now this is the same Handler because next the library exports one kind of global catch-all route Handler but we have to handle get requests and post requests and those are the function names in the new app directory all right once we have this mocked we can go ahead and start our Dev server and then jump over to test it and see what we have so this is going to load up our application and it's just going to show a blank page to start before we go and test the authentication we have to add an environment variable for next auth to work it requires a secret that it's going to use to encrypt the JWT now you can get away if you're only doing stuff client-side by not setting this in Dev mode but because we're going to be doing a bunch of server-side rendering we have to set it even in development jump over to visual studio and create environment dot local file you're going to set next off Secret and this can be anything you want in production you're probably going to want to do something like this to get a pretty random string that you can set as the secret now here we don't need it to be anything special because it's in Dev mode we just need to set something so the JWT is signed consistently I like to restart the dev server just to make sure we have everything locked in and then from the blank page we can go and manually navigate to the route we created API auth and the next auth has a sign in Handler for this route inspect the page so we can see what the network request is doing and then we'll sign in so in the network request here this is the request that we made and you can see that it was a post request sent to API auth callback credentials and the result was a 302 found which meant it redirected us back to the home page you can see the redirect sent here in the response headers and this is the cookie that's set as your session token which is used and you can go to the application Tab and take a look at the cookies that have been set and here's your session token which is what next auth uses to do the authentication and then it has a cross site request forgery token that it holds on to as well as the Callback URL that it saves to know where to redirect you all right so we successfully logged in we went to the login page we got a 302 were redirected back and you can see that we have the values that have been set which say authentication has finished but this isn't really useful for two reasons one we haven't authenticated against our database two we need to get the session information in order to really do something with it so let's talk about getting the session data there are three places you're going to want to get the session data and maybe do something about it one place is server side in a react server component since we're in the app directory that's where you might need the information you also might need a server side in any of your API routes and lastly you also might want to fetch it client side and do something with it there and these are three different use cases and two of them happen on the server one happens on the client with the latest version of the library getting the session information is actually very easy to do on the server to get your session in a react server component you're going to use the get server session method so here because this is a react server component which everything in the app directory is by default you can simply request the session make sure you await the get server session function import it and the options are the same options you use to create your next auth setup in the route now this is needed because if you change the session or the JWT functionality the same method needs to use those same options so here it's easiest just to pass through the exact same way you're creating and setting it up to make sure it's always consistent so just go ahead and import that in save it and then here we'll just do a json.stringify so we can take a look at what's in the session jump back to Chrome and there we go there's a user object name Ethan email test at test.com and if you look at what exactly we're returning here in our route authorize credentials there's an ID there's my name and then there's an email test.test.com awesome so this is how you can get the session information on the server and it's very similar to do it in an API route let's go ahead and do that now in an API route we'll just create a new file at the top level make sure you name it route.ts and here we're going to do the same thing we're going to get the session by calling git server session because this function runs on the server we're passing through the off options and then we're gonna just return a Json response to say if we're authenticated or not and you can go ahead and do like a console.log get API with the result of the session so you can see it so in Chrome an easy way to test this is just to go to slash API and it says authenticated true as a Json response and we can look at the terminal to see our console log get API with the session as well now this also has an image key that you can use for an avatar or something like that that's default in this JWT all right so now you can get the session information server side but what if you want to get it client-side to get a client side we have to do a couple of things first next off expects there to be a session provider that you wrap your application in and then you use a client-side hook to get the session information now what the hook is going to do is it's going to make an HTTP request from the client to the server to get the session information because the client can't decode the JWT locally it has to ask the server to do it so if you're ever working with session information the first time you make the use session request there's going to be some latency added as it decodes it and stores it in the provider let me show you what I mean the first thing we're going to have to do is make a new providers file and this is the next Js recommendation for using providers that are client-side only here we're going to set up the session provider and we're just going to export this as a wrapper around it and pass through the children and then in our layout we can add in the provider here and now our entire application is wrapped in this client provider so now we can go ahead and make a new client component let's call it user and pretend like this is something you might use to display user information export and we're gonna get the session using the use session hook and then we'll just do something simple again by using Json to see what's going on inside the session so now we have to use this let's go ahead and toss it here and we'll just differentiate this with a server session and a client session and we'll remove that so a server session this is what it looks like and then the client one it looks very similar there's a name still there's an email and then it also has an expires key so you can look up when this JWT will expire now if we refresh I want you to look to see what the latency is foreign see that the server session is immediately available because it loaded when the page loaded but the client session it first had to request the server to decode the JWT get the session information and then it can display it so when you're using the client use session hook the first time you call it in your application it's going to return undefined or null and you're going to have to handle that we can see this by jumping into our user and just logging what it does we'll do client session and then we'll look at the console clear it and refresh and the very first client session call is undefined and then once it loads it actually has the information so this is a big difference between the server side getting the session information and the client side getting the session information in the app directory I would recommend always trying to get this information server side because it's so much faster and then you can pass this information throughout your application and you can even see the provider for the client-side calls to make your application more performant awesome so now you can get the session information the next big thing here is we have to hook this up with Prisma and actually go ahead and look up the user information in our database jumping back into our app we'll take a quick look at our schema so we have a model that's a user there's an ID an email a password and a name the password of course is going to be salted and hashed some people might name this salted password or hash password but because we all know security here and you're writing a good application of course a password is going to be hashed never Store password in plain text so I like to just name it password because it's short and sweet next auth doesn't have the concept of registering a user in it when you are building your application the registration part is kind of up to you to implement this is because for things like magic links and email login or oauth there is no registration phase people just sign up click the login with Google button and if they don't have an account it's just in time provisioned with credential authentication we have to create the user in the first place this would normally happen on some sort of register page but instead of doing that we're just going to seed the database with the test user as you go through and develop your application you're going to find shortcuts like this really helpful to speed up development instead of having to register a user every time you spin up your Dev environment just see the database with one and then you can always instantly log in to see the database we're going to look at our seed file this came from our Prisma starter and you can see here we have a password that just pasted in now I forget what this password is because it's already been encrypted so instead of doing this let's go ahead and set up the mechanism for encrypting the password because we're going to need it for decrypting it anyways to do that jump over and install bcrypt bcrypt is the best library for encrypting and comparing passwords for some reason it doesn't come with types yet so go ahead and add the types as well and then instead of using this password what we're going to do is we're going to create a new password and we're going to call the hash function from bcrypt and we're just going to do test with a number of salt or rounds 12 is a pretty good number and then instead of passing in this password we're going to pass in this one make sure we await the result now that we have the seating ready to go let's go ahead and generate our Prisma client create a migration if you haven't done one already and then see the database all right at this point I like to poke around the database and make sure it's doing exactly what we expect so here we have our user table with an email test.test.com and a password that is correctly hashed so with the user created in the database now we can go back to that authorize function and add in the actual authentication code let's jump into the API auth route and instead of mocking this out we can now properly handle the authentication the first thing to check is if there are no credentials and they didn't pass in the email or they didn't pass in the password go ahead and return null null tells off JS that there was a invalid credential set not that there was an error not that we couldn't connect to the database you can throw exceptions in that case but this is specifically the credentials the user provided simply weren't correct once we've checked that we know there's an email and password now so we can go ahead and look up the user await Prisma user and we can use find unique because we know that the email in our database is going to be unique now we've looked up the user but we may or may not find one so if we don't have a user go ahead and return null by bailing out early in the function call you don't have to have nested ifs you can just say if this isn't the case then return if this isn't the case then return this keeps your code easy to follow so now we know we have a user because we found one with the email they put in let's go ahead and check the passwords you can't use the hash function again and do a string comparison because decrypt salts things you're going to get a different hash each time you have to use B Crips compare functionality to see if the passwords are the same so here const is password valid we can await the compare function and the first thing is the user string so we'll do credentials.password and the second is the encrypted one so we'll do user.password and then well if it's not correct return null and then lastly we can actually return the information we're looking to do so here we have to cast the ID to a string because that's what's expected so user.id is a number we'll just cast it to a string and then we'll also return the email and the name save it and let's go ahead and see if this works uh oh well this is a weird error this very unhelpful error will pop up time to time when you're doing development in the new app directory and it's really hard to figure out exactly what's happening but the trick The Secret of looking at this is in route TS we're loading bcrypt and what next is trying to do is it's trying to resolve and load the bcrypt module into the browser now that's not what we want at all b-crypt should only be used server side we're just using it for encrypting and comparing passwords all on the server but it's trying to load it into the client bundle so we have to tell next.js do not load these things into the bundle luckily there's a way to do that jump over to the next configuration file and there's a key called server components external packages also under the experimental flag and here add in for good measure the Prisma client and bcrypt these are two libraries that we do not want added into our client bundle these should only ever be run on the server you have to restart your Dev server whenever you change the file and now we can go back here refresh and see if it loads awesome we're back in Action let's go ahead and pretend like we logged out by deleting everything refresh the page and our session should all be null and now we can log in again and now because we're properly authenticating against our database if we type in something random testing at hello.com and try to authenticate it's going to kick us out so we know that you can't log in with anything anymore let's go ahead and log in with our test.tests user and then double check what our password is test and sign in boo success thanks Chrome so secure so here we now have a name that is test user that was seated in our database we have the email and it's correctly showing up server side and client-side so we've successfully done authentication with Prisma and postgres all right it's getting pretty tiresome running through and having to delete cookies to do all this testing stuff so we can add a couple of nice little things here to make our authentication smoother go ahead and create a new auth.tsx file and let's make some components that we can use for logging in and logging out so here we're creating a login button and a log out button and they're going to use the sign in and sign out functionality of next off and so in our page we can simply call this to make our life easier and now we have a sign in and sign out button so let's go ahead and test signing out you might have to restart your Dev server if it's not working immediately and then we can test signing in test that test Suite fully in business here the next tricky thing to do but is sometimes very useful is being able to store arbitrary keys in the JWT now what you might notice is here in the session there is no user ID it's not being passed into the session object and this can be very frustrating if you want to use the session object to store the primary key of the user object it's very common to create this session object and then when you're using it server side use the primary key to look up other information for example what blog posts has this user written if you can't get the ID back from the user then you have to look up the user by something else like an email and that might not be as performant it's much nicer just to be able to store the information you want into your session and use it that so to add custom keys to our session what we have to do is we have to modify two of the callbacks in next auth jump into the code and go back to our auth section and here after the providers there's a callback key that allows you to pass through a couple of different things the first one we're going to work with is session and the second one we're going to work with is JWT these two work in tandem one handles the creation and management of the JWT and the other handles the session object that is passed around and used whenever you fetch the session so you can read the docs to see what parameters are passed in here for the JWT call there are two that we care the most about the first is the token and then the second is the user now the user parameter is only passed into this function the first time the user logs in this might be through oauth this might be through their credential login so here this user token is not always going to be present it's only going to show up the first time they log in so we have to check to make sure there is a user object before we use it and then if there is one we're going to take some of the properties that we're returning from the user and add them into the JWT so to make this clear we're going to do this in a couple of passes first we're simply going to log let's call it JWT callback we're going to log the token and the user and then for the session this has a session and a token and we're just going to return the session as is but we're going to log them here too save that go over to Chrome and refresh the page and we'll take a look at the output in our terminal so here we refresh the page and we got the JWT callback with the token this has the name the email our sub these are JWT properties expiration um issued at expiration and then the user is undefined and then the session callback was called with the session this is the exact information you're seeing in your use session hook for example and then the token well this is the exact same token that's passed in here as it is in here okay so the flow goes first the JWT callback is called and it passes through the token and then the session callback is called when you have to get the session and it uses that token so in order to pass something through we have to return it from the authorize function pass it through the JWT function and then use it in the session to see this flow we're going to add a random key to our authorized function when it returns and it's really going to be random we're going to name it random key and the value is hey cool right so now that we're returning this we're going to see that it's going to show up on the user property when we log in so go back to Chrome go ahead and sign out and then go ahead and sign in again and before we log in we're just going to make sure we're here in our logs and then we're going to sign in and when we go and look at the server logs here's the JWT callback which is called after we've authorized and you can see that the user object is present and when it's present it has the ID email name and the random key and then by the time we get to the session callback none of that information is present anymore so what we're going to do here is if we have the user which means they just logged in we're going to return everything in the token as well as the properties you want to pass through so we're going to return the user ID because that's useful and then we'll return the random key which is user random key now the user here is either a user adapter or undefined and the user object is well it extends the default user it's not our user so we're going to have to cast it so const U user is unknown and this user what you should do is make it your Prisma user but because we added a random key here I'm just going to cast it as any so you can see how this works all right so if we have the user we're going to pass through a couple of the properties so now once again let's sign out let's sign in again and again we'll look at the logs here so now when that first JWT was called the JWT callback there is the user with the random information and then when the session callback is called here's the session but the token now has the ID and the random key assigned to it so the session callback now has the information we put into the token so now it's a relatively simple process to instead of returning the session as is we're going to pass through the properties and then the user is going to be everything in session.user and now we can add in the keys that we want to add in from the token so the ID and then the random key can be token dot random key and the token here is a JWT token but it allows you to have whatever properties you want on it we can remove the old session and now we can go back to Chrome and when we load the page we not only have the name and email but we also have the ID and any other random information you want to add into your JWT so this is so awesome now not only can you authenticate fully but you can add arbitrary properties to the JWT that you want to have commonly used throughout the application very commonly you're going to want to put the ID into the JWT but you can also put whatever you want in there now remember it's stored as a cookie so you have to keep the data small and under the maximum cookie size well you're almost all the way through the most important thing now is to ADD protected routes into your application this might be a page you want protected it might be an entire section of your application you want protected or it might be an API route let's see how we can protect all of these and the different ways we can do protected routes the first thing is Let's Make a route worth protecting we'll make a new file and we're going to call it page but put it into a new directory like the dashboard directory and here we can just have a very simple dashboard that's going to be super secret and if you go over to Chrome and you load the dashboard well it's not super secret anybody can get to this you can check that by loading an incognito tab and loading it up okay yeah cool uh not protected so to protect the page there are three different ways you can do it you can do a check client side server side or you can use middleware we're going to run through all three of these for the different use cases but I generally think using middleware is the best way to go to protect this client side the first thing we can do is cheat and just make the entire page a client page this isn't the recommended way to do it but it's going to get the point across and then you're gonna use the use session hook to load the session same as before but there's an on authenticated option that is going to be called and it's only called when the user is not logged in now remember the first time this Hook is called there's some latency because it has to decode the JWT server side and get back the resulting session which means there's going to be a loading State and that's what the status is for so if the status equals loading we're going to return a very simple loading or unauthenticated otherwise we can return the page itself so here we'll go ahead and just refresh the page and it says loading or unauthenticated first and then once the session loads it shows us the page now if we log out then we can go back to our dashboard and we'll see that this stays there and the not logged in console.log statement is triggered which is right here here you'll probably want to do something like redirect the page all right so that's client-side protection what we saw there's a latency involved and we have to load the session and because all of the pages are react server components by default it makes much more sense just to look at the session server side and do something about it there so let's go ahead and remove all of this and turn it back into a server component and then we can load the session the same way we were doing before make sure the component is marked as a sync to do this and then you can simply check if there's not a session what do you want to do because we're awaiting the response here we know there's either going to be a session or it'll be null if there is no session going back to what we said earlier you probably want to redirect the user to the login page something like using the redirect to next navigation and we'll call Api off sign in otherwise we'll show them the page so now if we are not logged in and we try to go to the dashboard instead of loading it at all it's going to redirect us to the sign in page and then once we've signed in if we try to go back to the dashboard it will show us the super secret page because we're authenticated very similarly you can do the same thing in an API route real quick we'll hop over to our route here if you don't have a session you can handle it with a 401 which is the proper way to handle uh API error like this so here we're going to return a new next response and the body can either be nothing or you can pass through a nice error let's do Json stringify error unauthorized to be nice and then the second argument we're going to pass in the status as a 401. this is how you can return other error codes besides just a 200 okay and also give a nice Json error for your Json API going back to Chrome we can quickly test this by loading up the API route because it's a get request our browser just makes it automatically we're authenticated so it works properly once we sign out we can try the API again and here in our Network log you can see that instead of getting that 200 okay we're getting a 401 unauthorized with the error unauthorized so this is the experience you'll want to give people who are using your API and it's how you can do protected API routes however there's a better way to handle checking authentication than on a page per page basis and that's middleware next auth has a middleware plug-in built in that allows you to easily see if a page or an entire directory of pages should be protected by Authentication to do that we have to create a new middleware.ts file at the top level of your next JS directory and the simplest way is to export default from next off middleware and this will add authentication to your entire application we can test that by going to our home page and because we're not logged in it kicked us out to the sign in page once you log in you're able to go back to the dashboard now remember this page didn't have any authentication to begin with all that page did was show what the session information was so just by adding this one line we protected our entire app now it's most useful to protect a sub part of your application you might want your home page to be unprotected login pages to be unprotected help pages to be unprotected all of those things are fine but you normally have a subdirectory app or something like it and you want all of those pages protected by authentication the easiest way to do that is with middleware configuration here you export an object called config and the key matcher says apply this middleware only to the routes that you've set up here so dashboard is going to cover our dashboard example so we can now remove all of this code and here you can set it up so this will protect everything in the app directory and all of its subdirectories as well so if you want to have an entire section of your application figure out what the top level should be and then you can add all subpass and you can keep adding more things here too as well so you can have other path or you can have help you can keep adding these things into the middleware to protect whatever parts of the application you want so now we go back to Chrome remember we disabled the dashboard there's no more authentication here so if we go to slash dashboard we're signed in so we can see it if we go ahead and just fake signing out by deleting our cookies and refresh the page it's going to kick us out to the login page because your entire application has been protected by middleware and there you have it you have now set up credential authentication end to end with next JS next off Prisma all in the app directory we're on The Cutting Edge here so you might find a couple bugs hydration errors or other things that you're going through it reset the dev server refresh the page and try again I think some of the most critical things here are the ability to pass through a user ID and primary key or other arbitrary information through the session token and also using middleware as the main way to protect your application if you like this video check out some of my other guides for how to set up Prisma or other things with next.js and until next time happy coding
Info
Channel: Build SaaS with Ethan
Views: 39,243
Rating: undefined out of 5
Keywords: next, nextjs, next.js, react, next-auth, auth.js, prisma, postgres, software, coding, software engineering, authentication, auth, authorization, JWT
Id: 2kgqPvs0j_I
Channel Id: undefined
Length: 42min 44sec (2564 seconds)
Published: Fri Mar 10 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.