React Persistent User Login Authentication with JWT Tokens

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
today we're going to add persistent user login authentication to our react app and we're going to do this securely without using local storage or session storage now i just logged in jane she's an admin user and she'll go to the admin page in the past if we reloaded our app she would have to log back in but now our app remembers what page jane was on and remembers that she was logged in likewise jane can visit a site like google and then she can come back to the site and it continues to remember that she is logged in this is because we added a persistent user login let's look at how this works in react [Music] hello and welcome hi i'm dave today we're going to add persistent user login authentication to our react app this is more of an intermediate level react tutorial than it is beginner and while this tutorial provides information on its own it is part of a series where we've built a registration page a login page and added protected routes as well as applying jwt access and refresh tokens for authentication and if you've completed my node.js for beginners course you've already built the backend that we're using in these tutorials i'll share links in the description to all of these and the source code for this tutorial too okay i have our react app running in chrome this is from the starter code and a link to the starter code is in the description below i also have the node.js rest api running in another instance of visual studio code and that is the back end so if you're trying to run this code along with me both of those need to be running now i'm going to log jane in and i've got the dev tools open already and we'll just quickly see in the console that jane receives user roles and an access token from the backend server now i'm not going to explain jwt's in detail like i did in the previous tutorial if you're not familiar with the jwt strategy i really recommend watching that previous tutorial i had on that where we applied the jwt strategy to this app but what we're getting is this access token and we're storing it in the state of the application you do not want to store access tokens in local storage or anywhere else that javascript can reach them because if you can put it there with javascript a hacker can also retrieve it with javascript now what you don't see here is that we also get a refresh token but the backend server issues that in a secure http only cookie and that's where the refresh token is at and then after a specific set amount of time what happens is our access token expires and that's what we see right here with the 403 and what we then have is sending the refresh token to the server to say hey give us a new access token and that was all implemented in the last tutorial so you see all of that here and now we can see our users list the problem we have and some noted in the comments of the last tutorial is that if we refresh the app or visit another page and come back we lose that state we lose the access token because it's only stored in memory we don't want to put it in local storage or anywhere else that someone else could grab it so the problem being you refresh the app you visit another page and come back and either one what happens is you lose the state and we're logged out we have to log back in on every refresh or anytime we come back to the app a persistent user login will eliminate that issue however it's not a secure so if you're really trying to make a secure app i suggest you live with having to log back in and maybe especially i guess something financial or otherwise maybe that would be the way to go but today we're going to add a persistent user login and we'll put a trust this device or remember me check mark down here as well so right now i'm going to close devtools and resize chrome and we'll start in visual studio code okay let's go to the components directory just highlight that and create a new file we want a new component and let's just call this component persist login.js now i'm going to go ahead and expand visual studio code and i think i'm even going to press ctrl b to hide the file tree for now just so we see all the code here the first thing i'm going to do is import outlet and that's going to come from react dash router dash dom we're using react router 6 in this application after that i'm going to import use state and i also want use effect and then after that let's go ahead and import our use refresh token hook that we created that is a custom hook that we created in the last tutorial and that's going to be in the hooks directory and after that we pick the use refresh token hook there we go and we're also going to need our custom use off hook and that's going to come from once again the hooks folder and then we're going to choose use auth there we go okay now we have everything we need imported let's start our function it's going to be persist login and then inside of here we'll start with some state so we'll have is loading and then set is loading we'll set this equal to use state and we're going to start with a state of true it's going to be a boolean true or false after that let's get our refresh function from our use refresh token hook okay and then we need to go ahead and grab the current off from our use off hook let's add a use effect now and this use effect is only going to run when the component loads so we want an empty dependency array and now inside use effect i'll scroll this up so it takes up the rest of the screen inside the use effect we're going to define a function i'm going to call this verify refresh token and it's an async function and inside this function we're going to have a try block and in the try block we're just going to await the refresh function that's going to reach out to the end point and take the cookie with it and that's the function we created in our use refresh token hook when it sends that cookie to the refresh endpoint then it sends us back a new access token and that's what we need and we want to do that before we get to the require auth component that would kick us back out and that's why we've got this component here so here is our catch now and we'll just console let's put console error and pass that in and then a finally and this finally block is a great place to set the is loading to false spell false there we go because no matter whether we have an error or not this finally block is always going to run so that will prevent us from getting in this endless loading loop now remember we just defined the function here so we're still going to need to call that we've also brought in the off state and that's important now when we come back say when we would reload the page or come back from another a page back to our app we will have an empty off state and so we need to make sure that we only run this when we do not have that access token we don't want to hit the refresh endpoint just every time we request a protected page we only want to do it when we lack an access token so here we'll check the auth and i'm going to use optional chaining and check the access token so if we do not have an access token then we're going to call this function verify refresh token otherwise we're going to just set is loading to false and we need to do that because we won't be running this function at all that would set is loading to false here so it needs to happen here if we do not run the verify refresh token and with that we have completed the logic that we need for our use effect but we're not finished with the component itself yet i'm going to add another use effect that we can remove later this is just for us to see what's going on so in this use effect once again it will only run well it's not going to run just at load time it's going to run any time that is loading state changes and that will let us see what's happening so now i want to have a console log and in here i'm going to put a template literal and say is loading so we know what we're looking at and pass in the is loading state after that i'll just copy this down and we want to look at a value for our auth token as well so i'll just call this a t but we can't just pass in the auth token here we're going to need json.string there it is and here i'll pass in auth again with optional chaining and then access token so we can see that in the console as well and save now we need to put the jsx in our component i'll scroll up again and of course that starts with a return and now i'll have a parentheses and we're going to use an html fragment and inside the fragment is where we can put some logic here so we'll check our is loading state and now let's create a ternary here and i'll put it on separate lines so the true response would be to say okay we're loading and if you had a component that had a spinner or something this would be a great place to put it i'm not going to do that today but just a suggestion and otherwise we'll pass that outlet that we imported from react router remember the outlet is going to represent all of the child components or child routes even inside of our persist login route and we're going to wrap this around all of our restricted or protected routes so let's save this and then before i forget we just need our export statement at the bottom so export default persist login and we should be finished now with our component so this really handles the logic to say if we need to go check that refresh token and that will help our persistent login only check that refresh token endpoint when it needs to okay let's go back to the file tree and now i need to go ahead and collapse the components directory but let's look in the hooks directory and find that use refresh token hook and let's quickly look at that i'll hide the file tree again with control b but what we're getting here is an access token when we send our refresh token back to that endpoint and here we're taking the previous state and then we're overriding the access token state so we have all of the state right here and remember when our user logged in she was also receiving roles currently we're not getting that from the refresh endpoint so we want to add that as well i'm going to just break these out onto separate lines so we can see each one and then i'll add in the user roles too so what we get here then is roles and this would be response dot data dot roles with a comma and we could save now if you're running the back end code like i am and you have completed that node.js for beginners tutorial where this is all built i'm going to pull over my instance of the node.js code here for a moment and we're looking at the refresh token controller now previously we were just sending back the access token so if you have this node.js code you're going to want to send back the roles which were already defined in here just send them back in the json as well you could even send back a username but what i would suggest not doing is do not send back the password that the user has there's no reason for that okay just wanted to show you that in the node js back end code for a second i'll pull that back off the screen and so now our use refresh token is receiving both the roles and the access token as needed and without those rules our user wouldn't be able to access anything because we're using protected routes and that was in a previous tutorial as well and we're going to see that next because we're going to go back to our app.js and import our persist login component so we'll start here here's the require auth component let's put it right underneath that so import persist login there we go i'm going to hide the file tree again with control b but scroll down now and we just want to wrap this around our protected routes and we can see that here we have some public routes that we annotated with a note there or a comment and we don't need to wrap it around those we want those to be available either way but we have this note where we want to protect these routes notice each route has a require auth component applied to it so this is what we want to wrap our persist login around so we'll start out with a route and then we'll just set the element attribute equal to persist login and we'll close out that component and i need to put another closing one there okay now i'm just going to highlight the closing route control x to cut and scroll down and put this before the catch-all route so right here now i can save and yes everything indented as it needs to so we can see that closing and now all of these are indented just a little bit and here's the opening with persist login and that's all we really need to do here with the routing again in the app.js when we're using react router version 6 we don't want logic here we just want to route and have the components all the logic should be contained within some of these others and our layout component as well as our persist component now both make use of that outlet that represents the children or the child components and routes if you will okay let's resize this now our app should still be running and we should now have a persistent login applied so let's check it out i'm going to expand chrome and then i'll do control shift i you could also right click and choose inspect at least on windows i'll log in jane once she's logged in we see some things over here in the console so we started out with is loading is true and we had our access token and then is loading as faults in our access token and that's really what we wanted and jane's here at the admin page so i'll go home we could go to the link page back to the admin page it hit a 403 because currently i have the access token expiring in like 10 seconds just so it would check and hit that 403 we got a new access token everything's good let's go ahead and hit refresh see what happens yep our page is still here jane was able to log back in let's see if we got some information up above yes we did we got is loading was true at first and the access token was undefined and then we have a console log statement in our use refresh token hook it looks like and our complete auth was just an empty object but then we had is loading as false and we have an access token and that's why we were able to see this and it takes us directly to the page that jane was on and that's because of some code we put in for react router in previous tutorials as well so remember all those tutorials are linked below if you haven't seen them we'll go back to home now here's a problem though what if jane doesn't sign out and we really haven't hooked up the sign out like we need to so we're going to do that next and this is the problem what if she doesn't sign out and then somebody else comes along and goes to the site she's logged in until that refresh token expires she can't really sign out unless we hit that log out in point and connect it to our sign out button so let's do that next i'll close the dev tools and resize chrome to get back to visual studio code okay i'm going to quickly drag the node.js code back over that we're running for the backend and let's look at the logout controller now it expects to receive a cookie if it doesn't it just returns a status 204 with no content because there's really nothing to log out if there's no cookie there it has nothing to delete however here we're going to get the refresh token from the cookie so we need to send the cookie back when we hit this log out endpoint and then down here further in the code you can see we clear the cookie this deletes the cookie all together and that will keep jane or any user from being logged back in when they don't expect it so we need to actually manually apply a way to log out and that way they won't be logged in until that refresh token expires which again the refresh token does need to be set to expire at some point okay let's go back to the file tree and i'm going to collapse a lot of stuff here it looks like but we're in the hooks directory that's what we want so i'll highlight the hooks directory create a new file and we'll call this use out i'm going to make this a hook because we might want to use this log out in more than one place in the app in the future but i'm only going to put it in one place today okay we'll import axios but instead of just being from axios this is from an instance we've already created in our api folder and this will just be the axios instance there we go and after that we're going to need the use off hook again so we'll import use auth from use auth we're already in the hooks directory and now let's define use log out and this hook is going to first grab the set off from use off okay after that let's define a logout function because this hook is going to return a function and not a value and this function will be able to use then wherever we pull it into our app so we'll start out by setting our auth back to an empty object when we log out we want to empty out the current off state after that we're going to put a try catch block and in the try block we'll start with a response and we'll set that equal to a weight axios and here we need to hit that log out endpoint and then we'll have an object and inside the object we're going to set with credentials to true so that way we'll send that secure cookie back with this request to this endpoint okay after that we'll put a catch we'll grab whatever error might happen and we'll once again go console error and pass in the error and then in the end of this we're not quite finished because now we just defined the log out function we need to go ahead and return log out i'll scroll up here because at the very bottom then we also need to do export default use logout for our hook okay that's the full hook so now we need to import this where we're going to use it and that is back in our home component let's go back to the component directory and scroll down to home so we really haven't looked at this component in a couple of tutorials here so we were still pulling in our auth directly from the context and not using use auth at that point but we don't even need use auth now we're going to set the auth inside of use logout so i just deleted both of those imports and here we'll just import use log out and after that we will get rid of this set auth that we're pulling in from the auth context as well and underneath our use navigate i'm going to say const log out and set that equal to use logout now notice we already had a function named log out here so i'm going to change that existing function to sign out with camelcase get rid of these comments and the set off because we'll no longer need that we're already setting the auth to an empty object inside of our logout function coming from use logout so here we just need to await the logout and save now we need to apply sign out where we were calling log out down in the jsx with our buttons so let's scroll down to the bottom and put sign out there instead of logout so now we have updated our sign out button and we're actually going to make a request to that endpoint the logout endpoint in our backend node.js rest api it's going to delete the cookie that has the refresh token so there will be no refresh token and we're allowing our users to manually log out so that helps our security okay with this update complete let's resize visual studio code go back to our application we're going to sign out completely i'll go ahead and hit the console or control shift i to open the dev tools console i'll clear that out as well let's go to the login okay and now i'm going to get rid of login in the url so we're going to the home page which is a protected page let's see what we get in the console and see what happens okay we were directed back to the login as expected and this is what happens when we don't have a cookie and seeing red over here is okay because this is absolutely what was expected we got a 401 unauthorized because our refresh token didn't exist we don't have a cookie so is loading is true the access token is undefined it hits the request or the end point for the refresh to get a new token but we didn't have a cookie to send and then it fails because of that and so is loading is false but our access token is still undefined and we're kicked to the login so that first test works as expected now i'll clear out the console again i'm going to log jane in and once she's logged in we have an access token everything looks good i'll clear out this console again and let's refresh okay so we start out with no access token no auth at all our state is an empty object and then we use the refresh token and we get an access token and this of course is the access token that we get back from the use refresh token hook now our is loading is false and we have an access token and it let us go right back to the home page i'll do that again and we should get the same results so we were temporarily loading and now we're at the home page if we go to the admin page and we refresh it should be the same result but we should now go right back to the admin page and we do okay i'm going to pull over our node.js backend one more time there's one more test we want and this is going to be when our refresh token expires so actually our refresh token is issued from the auth controller when a user logs in right now i have it set to one day typically your access token would be 15 minutes or something short but the refresh token usually lasts longer however an access token could be very short here we've got 10 seconds and a refresh token might be as short as 15 minutes or something like that on a financial site you never know so we'll set this now to 15 minutes or 15 seconds sorry because we want to see the result when the refresh token is expired so we have a cookie but the token inside the cookie is expired so now let's try that out i'll pull this back off the screen and we'll come back over here and we're going to need to log out totally signed out clear everything out there and now we're going to need to log in and get a token so here's the login there we go so here we go with jane and she's logged in now and she gets a refresh token but that refresh token is going to expire very quickly so i'll clear this out and maybe we'll still see everything nope there we go so we're logged in right now we got a 403 so it had been 10 seconds the access token had expired and we got a new one because the refresh token was still valid but at this point now it's been 15 seconds the refresh token will have expired so now if i refresh our page we should get booted back out because our refresh token has expired and that's what happens notice we don't get a 401 like we did when we didn't have a cookie at all we do have a cookie it's just that our refresh token was expired so we got a 403 here otherwise the user sees the same result no refresh token is valid so they're kicked back out here to the login and that's great so everything's working when jane's logged in and has a valid refresh token it works and if jane is logged in and has an expired refresh token it doesn't work i'm going to pull the node js code back over here quickly and i'm going to change that 15 seconds back to one day for now and save because we're finished checking with that after that so we're back to where our refresh token won't expire quickly after that we have another security issue to talk about so we have it now where jane can log out but what if she forgets to or what if any employee forgets to and say they're on vacation they're in a hotel lobby they log in they forget to log out and now that refresh token is good for an entire day in our situation and so anybody else that goes to the site will be logged in as jane or whichever employee use that hotel computer the uh the one in the lobby that's available to everybody that's not good so we need to put a trust this device check here and that will allow jane or any employee to go ahead and say trust this device or not so if they're on a public computer then they can just say no we don't want to trust this device and that will disable the persistent login altogether but if they're on their home computer or office computer they could check trust this device and then they'll be able to stay logged in as long as they have a refresh token that hasn't expired so let's go ahead and once again close the dev tools resize chrome come back to visual studio code and expand it from here i'm going to collapse the components directory and go to the context directory we have an auth provider context here so click on that and currently we're just sending the auth and set auth in the auth provider but now we need to add some more state so now let's go ahead and say const and we're going to have not an object but another array there we go persist and set persist and i'm going to set that equal to use state but i'm going to want to expand this so ctrl b to hide the file tree again and now i'm going to say json.parse and inside of this i'm going to have local storage now earlier today for this tutorial i said we wouldn't use local storage for access tokens or refresh tokens and we're not this is only going to hold a boolean to say whether we trust this device or not and that's just fine for local storage so here we'll put get item and let's call this persist if i could spell persist but if that doesn't exist we need to put a fall back here so we want to start with false if that doesn't exist so i'm going to press alt z so this wraps because even with the full screen it was wrapping off of the screen so now we have our full line localstorage.getitem and we're getting the persist value if it exists and if not we're just going to go with the default of faults now inside of our provider we also need to pass in persist and set persist so those will be available as well and we'll be able to pull those directly from use off just like we are auth and set off because it has the auth context in it with that complete let's go back to the file tree and inside of the file tree i'm going to open up the components directory and go back to the persist login component we've created today so now we're pulling in auth from uzos so we also need to grab persist here and with persist we're going to apply some logic in our jsx so this was already applying a little bit of logic based on is loading but now persist is essentially going to say whether we need to do this check at all so if we don't need to do this check we just want to show the outlet so let's say if we do not have persist and i'll create an extra line here because this will be a chain ternary but it's not a big complicated one so i hope you don't mind i like chain ternaries we've got an outlet this just represents all of the child components and routes and just like we are here but we're saying if persist is false just go straight to those components otherwise then we'll have a chain ternary here that checks that is loading this is what we were already checking if is loading is true show loading otherwise show the outlet so when this is all finished we just added this additional check here for persist and if it's false we're just going to skip this check all together and go straight to the outlet so let's save okay and with this complete we now need to add our check box to the login so let's go over to the login component and at the top of the login component we're already bringing in set auth from use auth but now we're going to need persist and set persist here as well let's go ahead and hide the file tree and scroll down to our jsx we want to find our button in the jsx and we're going to put our check box right underneath that button there we are okay we're going to start with a div and then this div is going to have a class name and we'll set this equal to at least if you're going to use the css that i have to persist check now mentioning css this is not a css tutorial but i am going to include a little extra css in the index dot css file in this repository so it will format this correctly and you can check that out in the code if you want to if not you can format this in your own way now we need an input and this input type is not going to be text it is going to be a check box so now i'll hit return i want to keep each attribute on a separate line here for our input and i'll scroll up so we have some more room to add these other attributes so after the type being check box we'll set an id let's set this id equal to persist after that we'll have an onchange event and i'm going to set that equal to a function we're going to create called toggle persist after that we'll set our checked value equal to the persist state and now we're finished with the input but every input needs a label so let's create a label and this label has an html4 attribute that needs to match the id of the input so it is persist as well and then for the label i'm just going to put trust this device and save now before i forget i'm going to quickly go to the file tree scroll down over here select my index.css scroll to the bottom just to throw this css in for this checkbox and again you can reference this in the repository if you want to use my css and save so it's going to look like we want it to okay after that we need to scroll back up and put our logic up above the jsx in our login js okay so we're above the return for the jsx and now i'm going to define toggle persist as an arrow function and inside toggle persist we're just going to set persist and i'm going to use the previous state here and then it will just be the opposite of whatever it was so we can save that but we also need a use effect so let me scroll up just a little bit for some more room and now we'll put a use effect here and this is going to listen for whenever the persist state changes so we need to put that in our dependency array and if the persist state changes that's when we want to go ahead and store that value in local storage so this will be local storage dot set item and we're setting the persist value and then we're just going to set it to the persist state and save okay with that complete let's resize visual studio code and we'll resize chrome and we'll also inspect or control shift in the letter i just to see everything in the console got an empty console here but we do have a trust this device check and we should be able to toggle it as a matter of fact let's go to the application tab and if we expand this even just a little bit more we can see the persist value is true and if we uncheck our checkbox now the persist value is false so that's as expected also let's go ahead and go back to our console resize this just a little more and now i wonder how that got smaller let's go this back to around 860 or so let's log jane in and once she's logged in we said trust this device so we should be able to refresh and jane should get taken right back here to the home page yep everything's fine we go to the admin page we could refresh again and jane is still on the admin page so our persistent login is working let's go back to home and sign out we'll go back to the login once again and we will not trust the device when jane logs in and once we log in we're at the home page let's refresh and we should get kicked back out and we do oh we've got a message over here let's see what we've got it talks about performing a react state update on an unmounted component yes this is an issue something that i skipped over let's go back and fix this in our code i'm going to close the console everything's working as it should except we just have a little memory leak there that error is coming from the persist login component and we just need to put an is mounted variable in here so let's define is mounted with the let keyword and we'll set that to true at the top of the use effect and now we need to check that before we set our is loading state and that is because we're getting that memory leak from trying to set the state to an unmounted component and we keep this state inside of this component so what we'll do here in the finally is just say is mounted to ampersands that's saying if is mounted is true then go ahead and set is loading and we can find out if the component is mounted or not by using the cleanup function in use effect and now inside of this anonymous function that will run whenever the component is unmounted we'll just set is mounted equal to false now let's resize visual studio code again we'll come back and do the same thing here just to make sure we don't have that memory leak any longer okay we'll clean out the console and now we'll log jane in once again and we will not use the trust this device now she's on the home page let's go ahead and reload and we should get kicked back out yes and now no warning message we don't have a memory leak because we fixed that inside of that component okay i hope this tutorial has shown you how to add a persistent login without using local storage for the access tokens but still use it in your application when you want to trust this device 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: 188,546
Rating: undefined out of 5
Keywords: persistent user login authentication with jwt tokens, react, react user login, user login, login, persist login, persistent user login react, persist user login, persistent user login, persistent login, react js, reactjs, jwt authentication, user login authentication, login auth, login authentication, user authentication, jwt, jwt tokens, secure login, secure user login, persistent login react, react jwt, react authentication, stay logged in, trust this device, React tutorial
Id: 27KeYk-5vJw
Channel Id: undefined
Length: 37min 27sec (2247 seconds)
Published: Fri Feb 04 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.