MERN Stack Authentication with JWT Access, Refresh Tokens, Cookies

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
our mernstack project needs authentication and authorization [Music] hello and welcome i'm dave today we will apply user authentication and authorization to the back-end rest api of our mernstack project i'll provide links to example source code and all resources in the description below in lessons one through four we built a functioning back-end rest api for our mernstack project and in lessons five through seven we created the front end react app for our mirn stack project it should currently complete all crud operations for both notes and users at the end of lesson seven i left you with a viewer challenge to complete the new note form and the edit note form for the app i hope you did well and remember you can view my source code for lesson 7 in the course resources to compare your code to mine let's start today by quickly reviewing the difference between authentication and authorization while many use the terms interchangeably or simply refer to the abbreviation auth they are not the same things authentication refers to the process of verifying who someone is authorization is the process of verifying what resources a user has access to when we log in with the username and password we are verifying who we are and that is authentication after logging in our app users will be issued json web tokens also known as jwts while it's true that possessing a jwt confirms the user authentication has already taken place users send jwts back in a request authorization header to prove they are authorized to access the rest api endpoints and data resources today's starter code is the completed code from lesson four where we left off working with the backend rest api we are back to add authentication and authorization to the api now so the only change we're going to make in the package json right now is to go from lesson four to now save this as lesson eight in the name now let's move on to the server.js file not much to do here but we do need to add our auth route so it's going to look a lot like the users and notes route so i'm just going to click on line 29 press shift alt and the down arrow to copy down the user's route and above the user's route i'll put in the auth route and it's going to go to routes and then auth routes so we'll just save that line and we're finished with the server.js now let's go to the routes folder and when we highlight that we can create a new file and we'll name this auth routes.js and i'll start with the imports i'll just quickly paste those in and we can look at this it starts with express being required and then we're creating a router from express.router this is exactly what we did in our previous routes files and then we're bringing in the auth controller but we haven't created that yet so that will be coming up very soon after that we simply have a few routes to handle so we'll have router dot route and this will be the root route so it just has slash now of course this would be at slash auth already as we're directed to the auth routes but then after that there wouldn't be anything i guess to follow off as far as in the url then we'll have dot post and we'll just put this here as a placeholder now because we haven't created that auth controller yet now let's move down to the next route we'll say router dot route and this will be slash refresh so the full url would be the root url then slash auth then slash refresh after that we need the refresh route to be a get method so we'll just put an empty get here for now as we wait on that auth controller and then we have one more route so router dot route once again and this will be slash logout and now this one will be a post request so we'll put in an empty post method there to handle what we get from the auth controller after we create it then we'll have module dot exports and we'll set this equal to router and we can save the file for now but we have some methods to create in the auth controller before we move on to that auth controller we need to create a rate limiter for our login route the root route here in our auth routes so to do that we have one more dependency to add i'll go back to the package json and we'll scroll up where we can see all of our dependencies press control and the back tick to open up the terminal window i'll pull it down just a little bit then i'm going to type npm i and then express dash rate dash limit this shouldn't take long to install and now we see it in our dependencies right here inside of our package json now that we have that let's go to our middleware directory that we have here and create a new file and let's call this login limiter dot js i'll start by defining rate limit and we'll set this equal to require and then we'll have express dash rate limit that we see from our visual studio code intellisense after that i also want to bring in the log events middleware that we previously created so it's right here in this directory already so we'll just say require dot slash and it comes from the logger file after that i'm just going to paste in some code that we can go over i'll press ctrl b to hide the file tree and also ctrl z because we had a long line there that wraps so now we can look at the details of this code i'll get rid of that extra line but we're creating a login limiter with rate limit and pretty much everything you see in here then are options for rate limit that we're setting inside of an object let's just go over these first we're setting the time and we're setting this to one minute so 60 times 1000 milliseconds then we're setting the max rate limit so notice i put a note here this limits each ip to five login requests per window per minute and then we have a message if that is exceeded so too many login attempts from this ip please try again after a 60 second pause and then we have a handler and this handler is going to handle what happens if this limit is achieved and so we're going to log events here this middleware we created where we can see a log that there were too many requests and where it's coming from this gets written to our error log if we need to refer to that then we're also going to send this status with the status code and the message back and then these are settings standard headers and legacy headers that are simply recommended in the documentation for this middleware so i set those as recommended and now we have our login limiter that we will be able to use specifically in our login path so if we go back to auth routes now we should be able to include this by saying const login limiter and then we will require this and it's going to come from our middleware directory and then be in the login limiter file then we can use this specifically in a route so this is our login route the root route so we'll just say login limiter goes right here and then we'll put a comma and then this will be awaiting what method we call from our auth controller so now it's time to go back to the file tree so i'll show that again go to the controllers directory and we need to create auth controller.js now i'm just going to paste in the simple code for now and we will review this code but quickly we just bring in the user data model we also bring in the bcrypt dependency that we were previously using to encrypt the passwords as we stored them now we'll need to decrypt those with vcrips so we can read them and compare to what the user is providing to authenticate with we've also got a json web token dependency that we're going to call jwt so we're going to need to add that dependency and then we've got our async handler that we have used in the other controllers as well to catch any unexpected errors and pass those onto our custom error handler so i'll save this right now and we will come back and describe these empty handler methods that we have as far as login refresh and log out in our controller but right now before we forget let's add that json web token dependency so i'm back at the package json control and backtick once again type npmi and json web token and add this to our dependencies as well once i close the terminal window we now see it accessed and it's at slash auth which comes of course after whatever the url is and then we also have a public route for refresh which is slash auth slash refresh and this needs to be public because our access token our jwt that will give us access will have expired so the only way to get a new access token will be to have a valid refresh token that we send to this endpoint and then we have the log out method and this is going to be at slash auth slash log out and it can be public as well and we're going to clear a cookie at this log out route or with this logout method if we do have a cookie so we're exporting these three methods from this controller so before we put this logic in at least since we have the placeholders in place let's go back to the auth routes and put the rest of the information that we need from the auth controller into our routes so we're going to start out with auth controller and i'm going to copy this because i'm going to need it a couple of more times then we can say dot and we want login first we can use intellisense to help complete these after that in the refresh route we're going to have auth controller dot refresh and in the log out route we're going to have auth controller dot log out which is also in my list and we can save and we're now finished with the auth routes and we have the middleware that we're using our login limiter as well so now everything we need to complete is going to be inside of the auth controller for these three methods we just need to add the logic but before we do so i think it's a good time to review a little bit of information about json web tokens jwts jwts are referenced as a form of user identification which is issued after the initial user authentication takes place when a user completes their login and they are authenticated our rest api will issue the client application an access token and a refresh token an access token is given a short time before it expires for example 5 to 15 minutes a refresh token is given a longer duration before it expires possibly several hours a day or even days our rest api will send and receive access tokens as json data we will store access tokens in our application state so they will be automatically lost when the app is closed we won't put these access tokens in local storage or cookies if you can store it somewhere with javascript a hacker can also retrieve it with javascript our rest api will issue refresh tokens in an http only cookie this type of cookie cannot be accessed with javascript refresh tokens do need to have an expiration which will then require users to log in again refresh tokens should not have the ability to issue new refresh tokens because that would grant indefinite access the overall access token process involves issuing an access token after user authentication the user's application can then access our rest api's protected routes with the access token until it expires our rest api will verify the token with middleware every time the token is used to make a request when the access token does expire the user's application will need to send the refresh token to our rest api's refresh endpoint to be granted a new access token of course the refresh token is also issued after user authentication our rest api's refresh endpoint will verify the token if the refresh token is valid a new access token will be provided to the user's application and remember a refresh token must be allowed to expire at some point to prevent indefinite access we are back in visual studio code now before we can create the logic for our controller methods we need to create a couple of secret keys that we will use to create our access and refresh tokens that are issued by the rest api so to do that we're going to store them in our dot env file they will be environment variables so let's name those variables now we'll use all caps and we'll type access underscore token underscore secret and we'll have an equals and then i'm just going to shift alt and the down arrow because all i need to do here is change this to refresh token secret and this is where we will store both of those values so now let's create those values press control in the back tick to open up a terminal window i'm going to scroll up for just a little more room we can create our secrets right here in the terminal and we can do it with a module that is built into node so i'm just going to type node first and press return and now we're at a nodejs prompt inside of the terminal now i can require the module that we need so i'll say require and it is the crypto module after that i'll put dot random bytes in camel case so a capital b and put 64 inside there and then one more dot and then two strings so we call the two string method and let's supply hex now after this i'll press enter and we get a secret key and of course i'll change mine after this as well but we copy this you can do the same and after you copy it you can paste it in as your access token secret now we could press alt z to wrap the code you can see it's a fairly long string now we don't really need to type all that again we can just press the up arrow and it issues the same command so i'll press enter again and we get a different string back so i'll copy this one and i'll put it in for my refresh token secret and paste that in and now i'll save the dot enb file and close out of the terminal well i guess i could go back to the terminal quickly and press ctrl c to escape that node prompt and then close out but there is our dot env file now we have an access token secret and a refresh token secret so now let's put our logic inside of the login method of our auth controller and i'll start out with the basics here let's go over this we are expecting a username and a password to come in when a user logs in this is the authentication process and so we'll say if we do not receive a username or a password we will send a bad request status which is a 400 and a message that all fields are required then we'll look for the user in our mongodb database in the users collection and if we do not find a user or if the user is not active remember we have that active status for each user that dan d will be able to deactivate a user even if we still want to keep them in the database because they are linked to notes so if the user is not active or does not exist then we'll send the 401 unauthorized if the user does exist we will try to match the password then and we're using bcrypt to compare the password that we received to the password that is stored in the database and again if there is not a match then we'll return a 401 again which is again unauthorized after this i'll scroll for some more room and we need to create our access token our refresh token and our secure http only cookie so let's start out by creating this access token and so you'll see i'm defining an access token variable and now i'm using the jwt that we created above when we imported the json web token dependency so this is jwt dot sign and now we're creating that access token here so it contains what looks like an object and we've got user info and inside that user info we have username and roles so this information is being inserted into that access token and we would need to destructure that access token when we return that information in the front end application as well so all the front end we'll have in state is the access token until we destructure it or decrypt it and pull this information out and notice we're now passing in our environment variable that has the access token secret to create this now here in development i'm only setting the access token to 10 seconds at first so we'll see it expire very rapidly but when we're finished we're going to want to set this to something like 15 minutes likewise right now i have the refresh token at one day and will probably come back and modify this to even a shorter amount of time when we're testing it out just to make sure it works because we won't want to wait a day to see how it reacts when it expires however with dande's user requirements and user stories we have he wanted users to have to log in at least every seven days so we'll probably eventually during deployment set this more like seven days and that way they won't have to log in every day if they don't log out okay now the cookie the create secure cookie with the refresh token we've just created above so now we have a response with a cookie we're naming it jwt and we're passing in that refresh token now here are the options we want to make sure we have set so http only is set to true and this means only accessible by a web server likewise secure is set to true now this means https same site we set to none so cross site availability is a possibility and that's because we will be hosting our rest api possibly at one server we may have our application at another server so we do want to allow a cross site cookie now max age here we're setting this to match the refresh token so this would be our seven days actually if we look at this this is one thousand milliseconds now this is sixty so sixty seconds and one thousand milliseconds so there we get one minute and now we have 60 times one minute which would be one hour 24 hours in a day seven days in a week and so that's how that max age is calculated then we are sending back the access token in the json so the client application receives the access token the server sets the cookie so the client application with react never actually handles the refresh token inside of this cookie but we will ensure that when react sends a request to the refresh endpoint that this cookie is sent along with it now let's move on to the refresh method inside of this auth controller and we'll once again start out with the simple stuff at the top where we're expecting a cookie with the request and if we don't have a cookie named jwt as we expect then we're going to send a 401 unauthorized if we do have it then we're going to set the refresh token variable to that cookie and after that we need to use our jwt dependency to verify this token so then we're going to call jwt verify as you can see right here after we set that refresh token variable and then we pass in the refresh token variable and then we pass in our refresh token secret that we have inside of our environment variables now we're going to use that async handler that we're using to catch any possible async error that we did not expect but notice we've already done the verify process here we've already completed the verify process i should say and if an error is created it is passed in here as an argument and so this async handler is going to catch errors that we did not expect but if there's an error from the verify process it's right here and so then inside this function we're responding to that error and if we do have an error there we're going to send a 403 a little different than a 401 403 is a forbidden response and that's the message we're sending along with that then we once again look to see if we have a user and if we do have the user from the decoded username that should be inside of the refresh token then we're going to say or if we do not have that user then we're going to say 401 unauthorized again hopefully we do have the user and if we do we're going to create a new access token with that username and with the roles and then we're going to pass in that access token secret again because we're creating an access token right now i once again have the access token expiring in 10 seconds which we would change before deployment this is just for development and we're responding with the access token and again this is because the refresh endpoint should issue a new access token if the refresh token is valid and now let's move on to the logout method which has the easiest logic of all we'll just go ahead and add this in i'll press alt z because this one line does wrap but once again check for cookies we're expecting to get that http only secure cookie that has the refresh token and if the cookie doesn't exist with jwt in it then we're just going to send a status 204 which means yes the request was successful but there is no content otherwise we're going to call clearcookie if there is a cookie so we will remove that cookie when the user decides to manually log out and we'll look for that jwt cookie and you have to pass in all of the same options that you did when you created the cookie and then we'll just respond with a message saying the cookie is cleared so this would by default be a 200 status response meaning successful and the message cookie cleared after that we're just exporting all three of these auth controller methods now while we've created the auth controller logic and it does handle the end points it doesn't protect the other endpoints yet with those tokens so we need to create the middleware that will verify a valid token every time we make a request to a protected endpoint so let's go to the middleware directory and create a new file now named verify jwt dot js i'll start this file by defining jwt and requiring that same json web token requirement or dependency that we have added to our project and now i'll define verify jwt and this is middleware so remember it receives a request response and next and then we'll go ahead and have an empty function here and i want to put the module exports at the bottom before i forget so say module exports equals verify jwt and now that we've done that let's go ahead and look at what we'll get first inside of this middleware and i'll put this right at the top and then we'll break it down here i'll press alt z so it does wrap we're defining the auth header because we're going to look at the header of the request and make sure there is an authorization header either with a lowercase a or a capital a so we've got the or here because there is no requirement or standard for hey it must be lowercase or it must be uppercase so it's best to look for both and of course we're creating this application full stack mern project where we have control over that but this is a good practice so you're always looking for either the lowercase or the uppercase authorization header now what is required is standard for providing the authorization header is what's in the value and it should always start out with the word bearer with a capital b and be followed by a space and after that space should be the token this is all in a string so we can check that by checking the auth header we've defined above and then verifying it starts with this string bearer and the space and if it does not we can reply with a 401 unauthorized response and after that we can go ahead and grab the token so we define our token which is the access token and we get it by splitting that same auth header string that we were looking at above and we don't want the word bearer or the space we just want the token that comes after the space so we split on the space and take the second value of course the first value being stored at the zero position this would be at the one position now that we have the token we pass that into the jwt dot verify method and we pass the token in and we verify it with our access token secret and then we have our function here once again if we have error we note that error and then after we do that we send the 403 forbidden response otherwise we should have decoded values and then we'll set the request.user to the decoded.userinfo.username and the request.roll should be the same and then we can call next at the end of this now next is the part of the middleware that calls either the next middleware in line or we'll move on to the controller if that's where the request needs to go with that complete i'm going to remove my semicolons just to once again stay consistent as i'm trying to break myself of that habit and then we need to apply this middleware again our verified jwt middleware to the routes that we want to protect and so let's move back down to our routes directory and first we're not going to apply it to the auth routes but let's look at the auth routes because here we brought in our login limiter middleware notice how we could apply it to just one route we just put it here after our post method and we put in the login limiter comma and then we call the controller method now that's possible if you want to just apply it to one route likewise in the server i'll scroll down to our server we applied app.use and then say our express.json middleware here is applied to the entire app so that came before any of the routes that was applied to everything so our dot use method could actually be used to apply everything to all of the routes inside one of our routing files so let's look at the notes route and we can bring in our verify jwt so i'll say const verify jwt we'll set this equal to require and then two dots in our middleware directory and then there's our verify jwt and now instead of applying it to any one specific route here i'm just going to say router dot use and i'm going to pass in the verify jwt middleware now this applied this verified jwt middleware to all of the routes inside of this file and now i'm going to do the same thing so i'm just going to copy both of these and go to the user routes file and apply that here as well so i'll paste in the require so we've pulled in verify jwt and then we apply it to all of the routes in the file with those changes saved we're now ready to start our backend rest api and test out all the logic that we entered so let's go ahead and type npn run dev at a command line and get our api up and running it should be running on port 3500 now let's go to postman the last time we used postman was in lesson four as well and we can check these endpoints so we're going to go to http colon slash localhost 3500 that's running on our computer here in the dev environment and then we'll go to the auth endpoint so for that we need to do a few settings here let's put in the headers first we need to tell postman the content type that we're sending and that will be application slash json and then we need to go to the body tab and go to the raw selection here and we'll send that json we'll have an object and the first thing will be the username it does go inside of quotes here inside of postman and i'm going to send the user i've created called dan d he's our stakeholder and after that i need to send his password that's not what i need so i'll say password and his password is an exclamation capital lowercase d and then one two three four five just a simple one to test out here let's go ahead and send this to the auth endpoint and we'll see what we get back we've received our access token but we've also received more than that so let's see what else and here's cookies but this is not where the cookie that we've received is you can see if i click this it says no cookies received from server back here in the bodies in the body we have the access token however up here at the top right where it says cookies we have a cookie manager here is our jwt cookie if i click on that we can see we have received this secure cookie and it has our refresh token in it so this is a different token than we received in the body where our access token was now to send this back and it will send it with this path here as slash to all urls at our local host port 3500 however it won't right now because we start out with http and not https so we need to remove this secure just to test it out because that secure means it must be https which is what we would want in deployment but not right now as we test so let's save the cookie with that one change and close our cookie manager and with that change we can now go to the refresh endpoint and notice we did send to post of course with that auth request but now we're going to send with a get method here to the refresh endpoint and it will send the cookie that we just saved over here our refresh token is inside of that cookie so let's send and i have sent to the wrong endpoint i need to send actually to auth slash refresh there we go instead of just refresh now i'll send again and i expected to get a result there so let's look at our server and see what's going on and we can now see inside of the terminal that i didn't just send to slash auth slash refresh i also had a space which is represented by this percent 20 at the end so if we bring postman back up let's go ahead and remove that extra space that we can now see is there and now we should be good and yes we did now send to the correct endpoint and we've got an access token back because our refresh token was valid this is a new access token and if we were to send again we'll get a different access token and now we didn't receive a cookie with a refresh token but let's verify our cookie is still here so now let's go to the logout endpoint which would be slash auth slash logout and it should delete our cookie so by the way i haven't cleared out this raw json data here but we're not using it with these other requests it was only for the login request but it doesn't hurt to go ahead and leave it in there so now i'm going to send a log out and that goes back to the post http method and this should delete our cookie so let's send that it says cookie cleared in the response let's look at our cookie manager now and there are no cookies so that also worked now let's go ahead and log in once again so we'll go back to just the slash auth route and now we need this information in the body and it is a post request so we'll send in dan d's information he's logged in he's now received a new access token and we're going to use that to access either the notes or the user's routes or we could test out both just to make sure our verified jwt middleware is working and checking those access tokens so now to do this we need to go back to the headers and here we're going to add another header this is going to be authorization i need the capital a there at the beginning or just an a either way it would take lower case or the capital their authorization and now we need to start out this value with bearer and then a space and we can paste in our access token now this is not going to work at first and i can tell you why after we do it but we'll just check that our unauthorized is also working as planned so now this needs to go to a get request and let's just request all of the notes so let's send this and oh i said unauthorized it's actually forbidden because it was a valid or at least a cookie that we are not cookie a token that we expected to be issued but then it had expired and of course that creates the 403 forbidden so that is because we only have 10 seconds right now on our access token because i put it in a very short time here in dev mode i'm going to quickly close the terminal and let's go back to our controller where we have that 10 second setting up here where we first issued that let's see here's 10 seconds and then we had that same setting in the refresh token or refresh endpoint so i'm going to select both of those with control d set it to one minute for now and save i can open up the terminal again just so it we can see it saved and it restarted the backend server with nodemon and so we're running again on port 3500 now let's go back to postman and what we need to do here is go back to our auth instead of notes and get a new access token so it's going to be post we'll go back to the auth endpoint and after that we need to switch over here to the body and make sure we have dan d's authentication information so we send that in he gets a new access token which we can then copy without the quotes and then we'll take that over to the headers and i'm going to replace this access token inside of the header with the new access token we have one minute now to make this work so paste all of that in and then we'll go to the notes endpoint and it will be a get request we'll send our request and now we get all of the existing nodes now if this access token hasn't expired yet we should also be able to request the users and yes we've got all of the users but it probably will expire fairly soon so we'll check that out as well so let's go ahead and request the notes again and see if we still have any time left and we send yes we still got it so i'll just wait a few more seconds and we'll send it once again and i guess this is a long minute for me let's go ahead and try it one more time and now we're forbidden so we got the 403 response again so when this is forbidden that's when we need to send the refresh token to get a new access token and we will automate all of that inside of our react application and you'll find out how to do that with redux in the very next lesson 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: 37,437
Rating: undefined out of 5
Keywords: MERN Stack Authentication, mern stack authentication tutorial, mern stack authentication and authorization, mern stack jwt authentication, user authentication using mern stack, mern stack, authentication, authorization, mern auth, auth, authentication vs authorization, mern stack jwt, jwt, jwt access token, jwt refresh token, tokens, access tokens, refresh tokens, auth routes, cookies, secure cookies, mern authentication, mern authorization, jwt access tokens, jwt refresh tokens, js
Id: 4TtAGhr61VI
Channel Id: undefined
Length: 37min 21sec (2241 seconds)
Published: Tue Aug 23 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.