JWT Authentication with Access Tokens & Refresh Tokens - Node.js

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi this is tom das tech i'm tom and in this video we're going to take a deep dive into json web tokens how to use them as access tokens the problem with completely stateless applications and how to solve those problems with refresh tokens these and web tokens are made up of three base64 encoded objects concatenated into a single string the first string is the header the second is the payload and the third is an encrypted signature the header portion of the token contains metadata or data about the token such as the signing algorithm and the token type the payload is a json object that you want to store in the token there are reserve keys that you cannot use in your payload such as iss and exp you should never store sensitive information in the payload such as passwords the signature is generated by encrypting the header and the payload base64 strings with your private key the header or payload data change the secret will no longer match making the dwt invalid access tokens are granted to users when they have proven to be who they claim to be usually by logging in the bearer of these tokens can make subsequent requests and provide the token as proof that they are who they claim to be using jwts as access tokens allows your system to be stateless meaning the system does not need to have access to a stateful data source such as a database to verify and extract data from the token this is great for scaling applications but introduces a new problem we have to be able to force users to log out and make sure the user does not need to log in every few minutes the solution to these problems is refresh tokens refresh tokens allow the system to provide short-lived access tokens when a request is made that includes a valid access token that has expired access is granted for the request however if the access token has expired but the request includes a valid refresh token we can do a single trip to the database to make sure the user's session is still valid and if it is provide them with a new access token in this video we're going to take a look at the anatomy of json web tokens granting access tokens and how to use refresh tokens by watching the entire video you'll understand the concepts that power json web tokens and how to build your own secure authentication system using json web token this is a demo of what we're going to be making so the user interface is just to give a visualization of the responses and doing this through the browser helps us store the cookies and send the cookies along with each request so we have a login here and we have an email address test its test and we have our password so if we log in you can see that we get a new session the session id is two and it includes our email and it tells us that the session is valid and if we inspect the cookies we can see that we have a refresh token here and we have an access token so if we click this get session data this is just going to return the decoded access token so you can see here our session is two and it has our email address it's valid and we have an expiration date so our access token at the moment has a very short expiry it's currently five seconds so every five seconds when you get session data it's going to use the refresh token to grant us a new access token and you'll be able to see that by the expiry changing you can see here that it got a new expiry and we can hit this and then every five seconds we're going to get a new access token so if we log out we get our old session back and it tells us that it's not valid anymore and if we click get session data we're going to get a 403 and we can repeat the process again by logging in we get our session data and then we can log out and we can no longer get our session data so we're going to be building out the routes for these three requests and the logic to handle refresh tokens and automatically granting the user a new access token when their current access token has expired but they have a valid refresh token i'm back here in vs code and we can take a quick look at the code that we're going to build so we have our app here this app is using express but you can use any framework that you like the concepts of jwts access tokens and refresh tokens is the same no matter what technologies you're using so we just are going to allow our cores here from localhost port 3000 and we're going to start this server on port 4000 and then we have our routes we have a create session route we have a delete session route and we have a protected route so create session is going to log the user in delete session is going to log the user out and set the session to be invalid and that protected route is just going to return the session let's have a look at our create session handler so you can see here that it's getting the email and password from the body we have a get user function that's going to return the user it's going to check that the password is correct it's going to create a session it's going to create an access token and it's going to create a refresh token and you can see here that our access token has an expiry of five seconds you can make this a little bit longer if you like but it's easier to demo when the expiry is very short a refresh token is one year so the user will need to log in every year if their session is never revoked and we're going to set two cookies here we're going to set their access token cookie and we're going to set their refresh token cookie and then we're just going to respond with the session the logout handler is just going to set their session to be invalid and we're going to revoke their access token into their refresh token cookies and we're going to then send the old session back the get session handler is going to return request.user and this request.userobject will be available because we're going to use some middleware to attach the user to the request object let's dive into our middleware and we have this d serialized user function and this is where the magic happens so you can see here we're getting the access token and refresh token out of the cookies we're going to verify the jwt and we're going to pass in this access token and the paid load is going to user and we have this expired boolean if the user is valid we're going to attach the user to the request object and we're just going to return next so if the access token has expired and the refresh token is available we're going to verify that the refresh token is valid and if it's not we're just going to say payload is null and if the payload is null we're just going to return next we're going to use this get session function here and get session is going to make sure that our session is valid and if it is we're just going to return the session if the session is not available we're just going to return next but if the session is valid then we're going to create a new access token again with a lifetime of five seconds we're going to add a new cookie to the response called access token and again we're going to attach the user to the request object and the user is going to come from this verify jwt and we're going to verify the jwt that we just created and we're going to get the payload from there and then we're going to return next we also have this require user middleware and this require user middleware is just going to say if the request.user is not available then return status 403 so if the user is logged in you'll see indeserialize user the user object will be available on the request object and if it's not then we return 403 so if we come back to our index you can see that we're using deserialize user for all routes and then if we look at our routes you can see there's only two routes here that are using require user let's dive in to building this code so i have two directories here a start directory and a finished directory i'm going to be working in the finish directory but if you want to code along with me clone this repo the link will be in the description below and you can start coding along in the start directory and you can reference whatever's in the finish directory if you need to so i'm going to move into the finished directory and i'm going to open this up in a new vs code window and in this directory we have an api and we have a user interface and we have our generate jwt example that we saw earlier so we're not going to be doing any work in the user interface this is just a react app that we're going to use to log in get our session and log out so if we cd into the ui we can yarn install the dependencies here and if we move into our api directory where we're going to be doing all of our work you can see that we have a simple express server here and we're using cookie parser so we can pass cookies we're using express.json and express.url code so we can read the body of requests we've got app.usedcause and this is going to allow us to pass cookies in to our requests and this origin is going to set the header allow access control origin and that's going to be set to 3000 and so this will allow our client to trust our server and our server is going to listen on port 4000 and we are going to initialize some routes here so our routes are currently empty and we can figure out what routes we want we want one to log in we want a route to get the current session and we want a route to log out we also have this controllers folder here and i'm just going to make a new controller called session.control and our session controller is going to have a handler for logging in it's going to have a handler for get the session and it's going to have a handler for logout we also have this folder here called db and this is just a mock database that i've created and so we have users and you can see that we have one user here in the array and the email address is test test and the password is password and the name is jane doe we have a sessions object here and you can see the shape of that sessions object it's going to have a key and the key is going to be the session id and then the payload is going to include the session id email and whether the session is valid we have a get session function so this is going to take a session id and it's going to return the session if the session is valid we have an invalidate session function that takes a session id as an argument it's going to find the session if the session exists it's going to set valid to false and then it's going to return the session we have a create session function here and this just takes an email and we can also take a name in here and that can be a string we're going to generate a new session id so this is going to get the length of the sessions object and plus one we're going to create a new session payload we're going to append the session payload to the sessions object and we're going to return the new session then we can find a user from our users array with this get user function so let's get started building our login handler we come back to our routes and we want to say app.post and the post path is going to be api session because a session is the resource that we're going to create and we're going to create a handler called create session handler and we're going to need to create this session handler in our session controller so we can say function create session handler and this is going to take two arguments it's going to take a request and a response and we can type these from express we can say request comes from at type slash express and same with response we also need to export this function so in our request body we're going to expect an email and password and we can get the email and password out of the request body object we can get the user with the get user function and the getuser function takes an email so we can say if not user or user.password is not equal to password we just want to return a status of 401 and this means that the user has got their password wrong you obviously don't want to be storing passwords in plain text here i'm doing this for simplicity so we can set the status of 401 and send an error message that just says invalid email or password we don't want to tell the user that their password is wrong or the user doesn't exist we want this error message to be vague so we want to create an access token and then we want to set the access token in a cookie and then we just want to send the user back so to create an access token we need to create a signing function so we can sign a token so let's create a new folder called utils and we'll create a new file in the folder called jwt.utils.ts we need we need to import jwt from json web token and this is a package that i've installed when you do yarn install you're going to install all the packages that are in the package.json but if you want to see what packages we've installed there is a notes file here and you can see the install commands here so you can use a signing key to sign a json web token or you can use a private key to sign the token and then you'll use the public key to decode or verify the token so i've just gone on to google and i've said generate private public key pair and this is the key pair that i've got you want to generate your own key pair don't use this one because this will be committed to github and it's not used for anything other than this demonstration so we can collapse these here and we need to create a function for signing a jwt and we need to create a function for verifying the jwt we can export a function called sign jwt and this is going to take a payload and our payload is going to be an object and this is going to take a expires in string or number and this is going to be able to tell json web token how long the jwt should live for so we're just going to return jwt sign we're going to pass it we're going to pass in our payload as the first argument we're going to pass in our private key as the second argument and we're going to have some options here so the algorithm that we're going to use is rs256 and we're going to pass in our expires in so this is our sign jwt function complete our verify jwt is going to be a little bit more complicated so we can export another function called verify jwt and this is going to take our token which is going to be a string and if jwt dot verify cannot verify the token it's going to throw an error but we don't want to throw an error every single time we want to be able to just return null for the user and we want to be able to see if the jwt is expired or if it's invalid so we can wrap this in a try catch block and we can say const decoded equals jwt dot verify and we can pass in our token and we'll need to pass in our public key as well and we can catch that error and if our jwt is successfully decoded we want to return the payload and that's going to be the decoded value and we just want to say expired is false but if we catch an error we want to return payload null and we want to say expired is equal to error dot message so this should be error dot message includes the string jwt expired okay so we have our signing function ready to go so we can come back to our session controller and we can say const access token equals sign jwt and we want to put our user object in the jwt and we want to set our email to user dot email and we want to set name to user.name and we can set expireds in to one hour so now we want to set this access token in a cookie so we can say res dot cookie and we want to name our cookie access token you can name this cookie whatever you like we want to put the access token in the cookie body and we need to pass some options here so we can say max age and this is going to be 30 000 milliseconds which is 5 minutes we're going to set http only to true and this means that javascript won't be able to access our cookie we'll only be able to access this cookie through hdp which is a real advantage over storing your access tokens in local storage so now we just want to return reds.send and we can send back a decoded version of our access token so we can say verify jwt and we can pass in our access token and then we can call dot payload so let's start up our server and our user interface and test this out so we need to import our create session handler from our routes we can import from controllers session controller and we're going to import this as a named import and you can see our server has started on port 4000 so we can start our user interface we can test logging in here so we put in our email address of testtest.com and our password was just password and when we log in we can see the verified jwt here and if we check our cookies we should see an access token set in the cookies so if we want to come and inspect this access token we can come over to jwt.io we can paste the access token in the encoded part and we can have a look at the payload here and you can see our expiry is set to one hour so let's go ahead and implement this get session data handler so we're going to create a new route called app.get and we're going to say api session and we can create a handler called get session handler then we can import get session handler from our session controller let's make this function and again we're going to take request and response as arguments so to implement this get session handler we want to attach the user to the request object so let's make some middleware to do that let's make a new folder called middleware and we're going to make a new function in here called d serialize user and we're going to make a new function in here called d serialize user and because this is express middleware we're going to take a request and that is the type request and we're going to have a response which is of type response and we're going to have a next function which is of type next function and we need to export this as default so we can say const access token equals rec dot cookie and this works because we have cookie parser installed so we're going to use our verify function here but first we want to see if the access token is present so we can say if not access token then we're just going to call next function to just return next and this will finish this functions call otherwise we're going to decode the access token so we're going to verify it we need to import verify jwt we're going to pass access token in and this function here returns a payload and an expired boolean so we can say payload we can say if payload we're going to we're going to say rec.user equals payload and we need to add a ts ignore here if you use lowdash you can use lowdash's set function to set this property here and you won't need to use ats ignore and then we're just going to call next and we're going to return that as well otherwise we're just going to return next here so if we come back to our express server we can import deserialize user and we can say app.use do serialize user and this will get called on every request wait for our server to restart come back into the get session handler and we will now have the user on the request object so we can just say res dot send and we can say rec.user and we'll return this as well we also need to add a ts ignore here because user does not belong on the request object according to typescript so we can come back and click get session data and we can see that our request is made with our access token and we get our session data back so let's implement our logout handler so come to our routes again and we're going to make a new route called app.delete and this is going to be api session and we're going to make a handler called delete session handler and we can import this from our controller and if we come into our controller we can say export function delete session handler and again this takes a request and response object and all this is going to do is to unset our cookie so we can say response.cookie and we're going to overwrite access cookie with just a blank string and the max age is going to be zero and now we're just going to res dot send and we can send back success is true and we're going to return this as well so if we come back to our user interface we can click log out and we get success is true and the request should see that we unset a cookie and you can see our cookies here have been cleared so we can repeat all these processes again so we can log in we get a new access token we get some session data and we can log out so the problem with this is that our access token lasts for an hour if we need to log that user out and make sure the access token is invalid we can't do that the access token will be valid for the hour no matter what we do and so we also don't want the user to have to log in every few minutes and so we're going to fix this problem with a refresh token so the first thing we need to do is to give the user a refresh token when they log in so we can create a new token here and we can call this one refresh token and the refresh token is just going to have a reference to the session so we need to create a session and we have this create session handler here and this create session handler just takes an email and it just takes a name which is going to be user.name and we don't have these properties as an object so in our access token we're going to put a reference to our session id and we're just going to say it's equal to session dot session id and our refresh token is just going to have a reference to the session id so we can set our refresh token to be valid for one year because we're going to have control over whether this refresh token is actually valid server side and we can set this access token to five seconds you probably want to set this to something more like five minutes but this will make it easy for us to demo when we grant a new access token using the refresh token so we're going to set another cookie here and this toke this cookie is going to be called refresh token and we're going to make the contents our refresh token the max age is currently five minutes for our access token but we want to make the max age for our refresh token one year so we can do that with this number here and if you want to get this number you can just google one year in milliseconds and it will output something like this so this looks good but instead of sending back our verify jwt let's just send back our session and if we come back over to our user interface we should be able to log in again and we should expect two cookies to be set this time an access token and a refresh token and you can see here our session id is one and we have a valid session and we have two cookies set our access token and our refresh token and we can see these cookies set here and if we click get data you can see that our session is no longer valid so if we log in again and then click it we get our session we click it again within the five seconds and we get a valid session and then our jwt expires and we get a invalid session so we won't get session to return an error so let's make a new piece of middleware called require user a now require user middleware is just going to export a function called require user and this is going to take a request and response and we set our user on the request object in this deserialize user so on here we can just say if not request dot user we can return res.status and this is going to be a 403 and we can just send an error message that says invalid session we need to test ignore this otherwise we need to call our next function and we can return next if you're wondering why i put returns here all the time it's just so i know that nothing under this is ever going to be called if this piece of code here is called so for example if i didn't return this it would send the status and it would also call next so we can use this require user middleware in our route so we can say when you want to get a session you require user and we can also put this on our logout route as well so if the user is not actually logged in it will just return a 403 so we log in again and we can get our session wait for the session to expire and you can see here we now get a 403 error so let's make it so when we make a request with a valid refresh token it doesn't return a 403 it grants us a new access token so we're going to do this in our deserialize user so down here we're going to say this path here is for a valid success token and then we're going to create a path for an expired but valid access token so we're going to say our payload and we're just going to call this refresh is equal to expired and if we have a refresh token we need to get expired from this verified jwt function and we need to get refresh token out of our request cookies so if both of these conditions are true we want to verify another jwt and this time it's going to be our refresh token otherwise we're just going to say the payload is null and so then we can just check this refresh object here so we can say if not refresh just return next we're not going to have a user to attach to the request object otherwise we're going to get the session and we're going to get the session with the refresh dot session id because remember we put the session id in the refresh token and this get session function is going to check that the session is valid for us we need to test ignore this you could make verify function take a generic object here so you could tell verify.js what the decoded payload would look like but we're not going to do that here because this is not about typescript so if all these conditions are true we're going to grant a new access token we're going to say const new access token is equal to sign jwt i'm going to pass our session in here and again we're going to make our jwt valid for five more seconds we need to check to see if our session exists and if session doesn't exist then we're just going to return our next call again and if the session does exist we're going to set a new access token in the cookie and this new access token is going to be equal to our new access token and then we need to set our user on the request object again so we can verify access token to get the payload back and it's going to be verify jwt we're going to pass in our new access token and we're going to call dot payload so let's save this we can come back and let's log in again you can see we get an access token and our refresh token let's get user data and we should see this expiry change and you can see there that we got a new access token and this new access token is valid for five seconds and every five seconds we'll make a call to our database and we will grant a new access token so the last thing we need to do is when we log out is to remove our refresh token as well so we want to unset the refresh token and we also want to set the sessions valid to false so if the user has this refresh token set somewhere they can't use it to get a new access token so if we have a look in our database we should have a function called invalidate session so we can call this function here and we can pass in and we can pass in request dot user dot session id and we need to put ts ignore onto this as well and this invalidate session function returns a session so we can say const session equals invalidate session and we can just return our session here so we can check that it has been invalidated if we come back to our user interface we'll log in again we'll get our session data and we'll log out and you can see valid here is false and now we can't use our refresh token anymore one thing to note is that these access tokens and jwts are essentially plain text so anything that you have here is not encrypted so anybody that holds this token can see the payload so you need to make sure you're not putting data in there that you think should be encrypted so we don't want to put sensitive things in there like passwords that is json web tokens with access tokens and refresh tokens if you learned something here please make sure you like the video and hit that subscribe button i'm working my way to 1000 subscribers and i'm only at 300 so far so please make sure you subscribe thanks for watching
Info
Channel: TomDoesTech
Views: 4,396
Rating: 4.941606 out of 5
Keywords: jwt auth, jwt authentication, jwt authentication node js, json web token, jwt tutorial, json web token tutorial, jwt node js, json web token node js, json web token authentication, jwt token, jwt node js express, jwt auth node js, jwt authentication node js tutorial, json web token authentication node js, jwt auth tutorial, jwt auth express, jwt auth node js express, json web token auth, json web token auth tutorial, jwt, auth, node, refresh tokens
Id: XYjOteYbCMo
Channel Id: undefined
Length: 37min 16sec (2236 seconds)
Published: Mon Jun 21 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.