NextJS Authentication with Server Actions - Learn to build from scratch!

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
what's up YouTube Welcome Back to another edition of coding with Robbie as always I'm your host Robbie and in today's video we're going to build a full authentication system in nextjs we're going to be using JWT we're going to be using nextjs server actions we're going to be using HTTP only cookies we're going to be using Prisma as our database om and check it out here's what we're going to build so we got a login page here we can uh put incorrect details and get an error message we got a sign up page where we can sign up and the same thing we get error messages if our password or email doesn't meet the criteria and we can log in and we'll get redirected to this protected page that you can only see when you're logged in so I can refresh and see it if I go to the homepage we see this dashboard here and if I log out it's going to change based on whether I'm logged in or logged out and then if I try to go back to protected well I can't access it anymore and it boots me out and sends me to login and here's the coolest part since we're using uh next J server actions it's actually going to work without JavaScript so we can disable JavaScript entirely and we still got a working login system so if that sounds good make sure to like subscribe leave a comment and we'll get right into it let's go all right so let's kick things off by setting up our nextjs app and all the dependencies we're going to use so I got the nextjs installation page pulled up right here it'll be linked in the description you're just going to need node.js version 18 or better and basically any operating system and then they give us the command create an app right here so let's copy that and we'll go to our terminal we're just going to CD to where we want to place it and then run the command and this is going to ask you a few questions I'll name my app JWT I'll use typescript no es lint no Tailwind I will use the source directory and app router and I won't customize the Alias so once you go through that it's going to generate the app and once it's done we can CD into the folder and open it with VSS code so here's all the files they start us out with and then we can run our app with npm runev and this is going to start it up on Local Host 3000 so let's uh actually try this out and make sure it works and there we go we're running nextjs so now we're going to add our database and I'll be using Prisma to interact with databases so Prisma is pretty much the top om for node.js it's got 36k stars and a ton of people using it and it's really good I'd recommend it but I got the uh quick start guide right here so I'll link this in the description also and uh to get started we just got to install these dependencies so let's copy this command I'll open up a new terminal Tab and I'm still within the project folder and I'll run the command and this is going to get all the stuff we're going to need if we come down it says to initialize typescript but nextjs actually did that already so I'll skip this command and I'll come down to this one where we install another dependency so let's get that and then we can initialize Prisma with this Command right here here so let's copy it go to terminal paste it and then this last part is where we put what kind of database we're going to be using so I'll be using postgress and we'll run that and it's actually going to create a Prisma folder with a schema file in it and it's going to create a EnV file where we place our database URL so let's get started by updating that URL let's go to VSS code we'll go to the EnV and then we'll just change this to whatever our database URL is so I'm I'm running postgress locally um but just put whatever your database credentials are in here so I'll name my database JWT and save that and then one additional thing you'll want to do is just add this to your G ignore so let's go to G ignore and I'll just add. EnV down here and save it and uh let's go back to the instructions now so the next thing we got to do is model our data so let's actually copy this user model right here and we'll go to our prisas schema file and let's just paste it at the bottom and uh let's modify this so we'll keep the ID we'll keep the email but instead of name ours is going to have a password and it's not going to be optional so I'll get rid of that question mark and then we're not going to have posts so I'll delete that and let's save it and now there's a couple other steps we got to do so let's go back here we're not going to pull our database we're actually going to push it in a second but before we do that let's generate a Prisma client so let's copy this command and we'll go npx uh Prisma generate and this is just going to create a Prisma client that we can import and then uh use to modify our database so we just created the client now there's one other thing we got to do so this is another documentation page I'll pull up I'll link up but uh this is basically how you use the client in nextjs so if we scroll down they give us some code right here and we're going to place this in lib Prisma so let's uh create that so I'll put it in the app folder I'll do a new folder called lib and then within there I'll do a file called prismat dots paste it in typescript is going to complain I'm just going to ignore it in this file so let's go at TS no check and there we go we got our client set up for nextjs and now the last thing we have to do is just uh push our database so it actually syncs our models and stuff with the real database to do that it's npx Prisma DB push and this is going to create our database and sync it so it says it's done so we can verify that real quick so I use an app called table plus it lets me view databases and we can see right here we got the users table with ID email and password so we got our database set up now so we're just going to have a couple more dependencies so we're going to be using bcrypt JS and this is used for hashing passwords and comparing them so let's go ahead and grab this so let's go npm install bcrypt JS and then this is not a typescript package so we'll get the types also so npm installed D- saave Dev and we want at types bcrypt JS and there we go and the last dependency is going to be this one called Jose which lets you uh encrypt and decrypt JWT tokens so we can get that with npm install Jose and this one is typescript so we don't have to do anything additional and then just one thing to note is this is not the most popular JWT package so that would be Json web token but this one doesn't work within nextjs for some reason it uses some node module it can't import so it'll be using this one which is also pretty good all right so now we're going to create all the pages and API routes we're going to need so before we do that let's go into vs code and let's just kind of clear out some of the stuff we're not going to use so I don't care about the favicon or any of the CSS they start us out with so I'm going to delete all that and then I'll go within page. TSX and all this content I'm just going to delete it and replace it with a H1 tag that just says homepage and then we can delete these Imports at the top and then let's go to layout. TSX and I don't care about the font or the CSS so I'll delete that I'll leave the metadata but I do have to delete this class down here and there we go we kind of cleared out all the stuff they start us with so now let's create some new pages so let's uh create a folder called sign up and within there we'll create a file called pages . TSX and then uh let's go to this page. TSX in the root and just copy it into signup we'll rename this to sign up and then uh we'll wrap everything in a div and make the H1 say sign up and then we're going to need a form in here so let's create a form and our form is going to have an input type email with name email and then we're going to need a password field so let's copy that and just change it to say password so type password name password and then we're going to need a submit button so let's go button type is equal to submit and inside there we'll just put uh sign up so that looks pretty good for sign up so now let's do login we'll create another folder called login within there we'll create page. TSX and then let's just copy everything over from sign up and we'll just change it so everything says login instead of sign up and then let's create a third page called protected let's create protected and page. TSX within there and then in here we'll just have an H1 that says protected and there we go we got our three pages so let's actually try this out in the browser so we'll go back uh so I got slash login I got slash signup and I got slash protected so now let's create a couple API routes real quick so to do that we're going to use route handlers so you basically just create a route. TS file within the app directory so we'll Nest all our API routes in a folder called API and within there we're going to have sign up and log in and then each one of those is going to have a route. TS file and then if we go back to the documentation you can see that in these files we can put different methods so there's get post patch delete and uh update no it's not update why' I call it update what is it get post put patch and delete there we go so in our case we're going to be sending in data so we're going to want a post request so we can just copy the example right here paste it in and we'll just clear out everything except for the last line where we respond and we'll just respond with an empty object and we'll copy this and put it in both files and there we go we set up our Pages we set up some API routes and now we're going to hook everything up all right so now we're going to build our signup API route so I got the file pulled up right here and let's just kind of map out what we're going to do so first up we got to read data off request body and then we want to validate the data and then we want to Hash the password and then create a user in DB and then we want to return something all right so this first part how do we get data off this request well if we go back to the documentation the first argument uh this is going to get is this guy right here so we can just paste that in and this is going to be sent in Json so we just have to go go await request. Json and this will be the response body so we can assign that to a variable and then we'll actually get the email and password off of it so let's go email and password and we'll get that off of body so now we got to validate the data so we want to make sure it's a valid email and that the password meets our requirements so I'm not going to spend too much time on this I think this is a good task for uh chat GPT so let's do it over here let's go right me a function in JavaScript to validate a password and an email address so we'll let this run it's going to create it for us so here's our validate email function let's copy that and uh let's just make a new folder in here called helpers and you got to spell it right and then within here I'll create a new file called validate email .ts paste it in this is going to be a string and then we'll export default this and then let's create another one called validate password and uh let's see what it wrote for us this looks pretty good let's copy it and Export default it and specify that this is a string all right so let's see minimum eight characters one uppercase one lower case one number and one special character so that's pretty good so let's go back here and now we're going to validate the data so let's go if no uh valid email and we'll pass in the email or if there's not a valid password and we'll pass in the password and then if one of those is incorrect we got to return an error so let's copy this and inside the Json we'll go error we'll just go invalid email or password and then we're going to add a second argument to this where we can specify the status and let's make this a 400 error so there we go that's a good error and now we got to come down here if that does validate we need to Hash the password and we installed bcrypt JS to do this and if we scroll down a little bit they show us an example uh right here Auto gen a salt and hash so let's copy it and we'll go right here we'll make this a const we got to import the bcrypt library so let's go up here we'll go import bcrypt from bcrypt JS and then instead of hashing bacon we're actually going to put in the password they sent in so let's go password and we'll just leave that as is and now we have our hashed password and now we got to create a user in the database so we're going to use that Prisma client we created right here so let's go Prisma it's going to suggest the import we're going to go do user and then we're creating a user so do create this gets an object which has a key data we just pass in the data so we're going to do the email they sent in and then for password we're going to use the hash version that we created so hash and now this needs to be awaited on and then if this is successful we can just send the 200 back so we don't even have to send any data or do anything there so that looks pretty good all right so now we're going to hook this up to our sign up page so let's go to the page. TSX for sign up and then to hook this up we're going to be using nextjs server actions this is a new way to do it it's going to run it server side and it's pretty cool actually but uh to use these you basically just add this Ed server thing to your function and then uh you can connect it to forms and stuff so let's copy this we'll go to our page and we'll create one up top here and I'll rename it to sign up up and then let's just map out what we got to do in here so first we got to get data off form and then send to our API route and then redirect to login if successful all right so how do we get data off the form well the first argument this is going to receive is form data of type form data and then to get a field off of this you just go form data doget and then you put the name of the field you want to get so ours is email and password let's go email and we'll assign this to a variable called email and then we'll do the same thing for password change that to password all right we got our fields and now we uh got to send it to our API route so to do that we'll use the fetch API and I always forget how to do it have to look it up on a stack Overflow but here's an example so let's copy this we'll paste it in I'll change this just to resz and then we put our URL right here so ours is going to be HTTP colon localhost 3000 SL API SL signup and then we're going to have a problem so when we deploy this we don't want it go to Local Host so we should actually move this to an environment variable so let's cut it out and we'll go to ourv file and you're just going to put root URL is equal to and then we'll do Local Host so now let's go back and we can use that so let's go process. env. root URL plus the route so API sign up so that looks pretty good coming down it's going to be a post request I don't care about this accept header but I do want this content type application SL Json and then we just put the body down here so instead of A and B we're going to do our email and password which is the data that they entered in here and then down here is where we uh convert the respon response into Json so let's change that to resz and we just go hey await res. Json let's uh rename this to Json and now we want to redirect if it's successful let's go if res. okay then we'll redirect and this is going to come from next SL navigation and if it's successful we'll redirect SL login and that looks pretty good so now let's hook this up to our form and to do that you just go action is equal Al to and then the function which is called sign up and this should work so let's actually log this Json and then we'll try it out let's go to our page I'm on the signup page I'll enter an email and then I'll do a password that doesn't meet the criteria and hit sign up and we get a 200 back but if we look at the response it says Hey invalid email or password and it didn't redirect us so now if I put one that does work you can see that it uh redirected us to login and it should be saved in our database so let's check it out so it go back refresh and now we got a user with email and password and that's the hashed version all right so now here's where things get a little tricky how do we display our error message on our page and to do that I'm back in the documentation and I'm going to go to this uh serers side error handling down here and I'm going to scroll down a little bit and we have to use this use form State hook and then in order to do that we have to make it a client component so let's start with that let's copy this we'll go to our sign up page right here and put use client at the top and now it's going to yell at us it's going to say hey you can't use a use server in a use client so to get around that we have to move this function to a separate file so let's create one called signup action. TSX and then I'm going to copy over this whole function and I'm going to import redirect again and then I'm going to export default this and just rename it to match the file name and then we have to move this Ed server up to the very top and then we can go back here and now we're ready to use that hook so let's go back to the docs and copy the line right here I'll paste it at the top so this first argument is going to be what's returned and the second one is going to be the action and then over here we got to import use form state first argument is going to be our action so we call it our signup action and the second one is going to be the initial state so by default we're not going to have an error message so I'll just put an empty actually let's go undefined and then down here instead of putting sign up here we got to put the form action and we're going to get some errors that says hey wrong arguments over here so now when you're using it with the hook the first argument over here is going to be current state I'll just set that equal to any and then we actually want to return the error message in here so over here let's go hey this is going to return a string a promise string and then we actually got to return it down here so if the request is not okay then we'll return json. error and that's what our API route sends back there we go we can go back here and now we should be able to use that error message right here so I'm actually going to rename this to error we'll go hey if there's an error let's display it and let's try this out let's go back I'll go to my signup route and I'll enter a random email and I'll leave the password empty it submits and we get hey invalid email or password and then I do enter some good details and it goes through and redirects me to login all right so now we're going to use a lot of the same Concepts over in our login API route so let's go over to that file and let's just map out what we're going to do so we got to extract data sent in validate the data and then we want to look up the user and then compare passwords and then if they're correct we want to create JWT token and respond with it so these first two parts are going to be exactly the same as our signup so we can actually just copy all the code so all of this and then we have to remember to put the argument so let's copy this stuff right here all right so we're reading the request body getting the email and password and then we're going to validate it so we have to import our validate functions that we created and then uh if it's not valid we say hey invalid email or password password and then if it is valid we want to see if there's a user with this email so let's look up the user and to do that it's going to be Prisma do user and this time we're going to do find First and it's going to get argument object where we're going to look for the user where email is equal to the email we sent in so we have to await on this and we'll assign this to a variable called user and now if there's no user we want to send the same error so let's copy this right here and go if no user then say invalid username or password and uh send the 400 status if there is a user we need to see if the password they sent in matches the hash saved on the user so let's go back to the bcrypt docs to see how to do that and they have an example right here so let's copy it and we'll assign this to a variable called correct password I guess is correct password is a little better so is correct password is equal to and we got to import bcrypt so let's just copy it from here and what are we comparing we're comparing uh the password they sent in which we have stored under password and uh the hash which is saved on the user as user. password all right so if those match we'll have true right here and if not we want to send error message so let's just copy this and we'll go if not is correct password then send that error and now down here we're ready to create our JWT token so we're going to be using Jose for that let's go to the documentation scroll down a little bit here's how you import it so let's copy that we'll import it and then go back and uh let's see we want to assign a JWT token so let's see how to do that here's an example so let's copy all this and we'll paste it in right here so we need a secret key that we're going to use to encode and decode it so we'll set this to an environment variable let's go to ourv I'll just call it JWT secret and we'll make it equal to some secret string that only we know and then we'll go back and now instead of this hardcoded string we'll paste in our environment variable process. env. JWT Secret we'll leave the algorithm as is and then down here I don't care about this um earned stuff so I'll get rid of that I'll leave the algorithm I don't care about issued at I don't really care about issuer don't care about audience for expiration time let's do 72 hours and then we actually want to add a subject so let's go set subject and this is where we store the user ID that's logged in so right here I'll just go user. ID and and it has to be a string so two string so this is going to create our JWT token with an expiration time and our user ID save to it so let's respond with that down here let's just respond with token and set that equal to JWT and uh there we go we created our API route so we're ready to hook this up now all right so let's hook this up to our login route now so let's go to our login page right here and we're going to create a separate file for the action so let's call this log an action. TSX and then let's actually go to our signup one and just copy everything and then uh let's go in here and see what we got we're getting the same email and password off it and this time we're going to be sending it to API login it's going to be a post request we're going to have application Json we're sending in the email and password and then we're parsing the response and if it's okay what do we want to do we'll redirect to protected and uh you know return an error if it's not good and there's actually one other thing we need to do here we want to set a cookie so we're going to be storing our token within an HTTP only cookie it's pretty much the most secure place you can put it JWT is not the most secure authentication method but it's the best you can do with it so how do we do that we can import the cookies uh thing from next slhe header this is a function and we want to set a cookie and then we give it a key so ours will be called authorization the value is going to be the token that gets returned right here so json. token and then we want to set some additional options so what do we want we want secure true so this makes it so it can only be used on https and Local Host uh what else do we want we want HTTP only that makes it so JavaScript and stuff can't access it um what else do we need we need expired which I actually have to copy the code so we get the time now and then we add seven days ours is three days so let's make that a three uh what else do we need path we'll go just slash so it'll work all over our site and the last one is going to be same site we'll set this equal to strict and this makes it so it can't be used from a different domain so it's more secure so we're setting the cookie and uh now let's hook this up so let's go back here and uh let's check out our sign up route again we're going to need this guy right here so let's paste that in and we have to make this use client and then we have to import this hook and instead of signup action we're putting our login action and uh was is it not importing it let's go back here I forgot to rename it so let's rename this to login action let's try again there we we go and uh what else did we do let me check out sign up again I think that's pretty much it so we can just display it so let's copy that we'll go back here and we'll go hey the action for this form is going to be form action and if there's an error we want to display it and then uh let's try this out so let's go back we're at log in and I'll put in my username and password I think I did Robbie at gmail I can't remember I'll put the wrong password I get the error message and then I put the right one and I get redirected to protected all right so now to protect this route we're going to use nextjs middleware and basically in the middleware we're going to check for the cookie and then validate it and if not we'll redirect them so here's how you do middleware you create a file called middleware dots at the same level as the app folder and then they give us an example down here so let's copy the example and we'll go back here we're going to create a file uh in the source directory called middleware dots and then paste in the example and take a look at it so down here is our matcher this is where our middleware is going to run so we want ours to run on SL protected and we'll also include any uh path that comes after that and then up here is the middleware function so what do we want to do up here we want to check for cookie and then validate it and then uh that's it if it's not good we'll redirect them so let's try this so how do we get the cookie it's going to be cookies again from next SL header it's a function and we want to get and then we just put the name of our cookie which was authorization we'll assign this to a variable called cookie and then we'll go hey if there's no cookie then uh we want to redirect them to the login page and then if there is a cookie we got to validate it so let's see how to do that so this time we want this guy up here and they give us an example down here so let's copy all this we'll just paste it in and then our secret is an environment variable so let's go process.env Dot and ours was JWT secret and then our JWT token is the cookie. value like that and then I don't care about the header but I do want the payload so we have to make this an async function so we can use a weight so let's go up here async and we got to import Jose so let's uh go back to the docs and see how you import it again right here all right and then I don't care about this stuff and uh this is going to throw an error if it can't decrypt it so let's actually catch that error so let's go whoops let's goey let's try to uh decrypt this JWT token and if it fails let's catch the error and uh let's just redirect them to login and then if it does work let's just console.log the payload so we can see it and let's try this out so let's go back here I'm on protected I refresh I can see it let's check console and I can see the payload right here so we got the expiration time and we got the uh the user ID right there so now if I clear my cookies where that authorization cookie is stored uh and then I refresh I'm going to get booted out and it's going to tell me to log in so I can log in again I get the protected I can refresh it works and there we go we just implemented middleware and then one last thing you're going to want to be able to do is display different stuff depending on whether the user is logged in or not so for example say we had a navigation menu and if they're logged in we just want to display dashboard and if they're not we wanted to display sign up and login so how can we do that let's uh go to the component I built right here so it's just a nav with some links and what we can do is just check for that cookie so we can go const is logged in is equal to cookies. getet authorization so cookies is a function doget authorization and then if that exists they're logged in if it doesn't they're not logged in so let's Implement that down here let's go is logged in question mark well if they are logged in then we just want to display this dashboard link and if they're not we want to display these two links down here save that so now we get hey dashboard when I'm uh logged in and if I clear my cookies and log out now I get sign up and login so easy as that and there you have it nextjs authentication with server actions so if you enjoyed this video make sure to like And subscribe this one took a lot of takes and it took a long time to make this video so I'd really appreciate it leave a comment let me know what's up and I'll catch you in the next one peace
Info
Channel: Coding with Robby
Views: 2,491
Rating: undefined out of 5
Keywords:
Id: INwOeQ0RagY
Channel Id: undefined
Length: 33min 14sec (1994 seconds)
Published: Fri Feb 23 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.