React Login Authentication with JWT Access, Refresh Tokens, Cookies and Axios

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
today we're going to look at react login authentication with jwt access and refresh tokens i'm going to log into this form and after i log in we'll see the off state in the console so let's expand the off state you can see i have a username a password some user roles and i have an access token i also received a refresh token but it's not stored in local storage and it's not in the auth state and javascript cannot even access it but when it's needed axios will send it back to the server when i request data let's see how all of this works and react [Music] hello and welcome hi i'm dave today we're going to learn about react login authentication with jwt access and refresh tokens 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 first built the registration form followed by building the login form and then we applied user roles to access protected routes with react router and if you've completed my node.js for beginners course you've already built the back end 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 in this tutorial i want to teach you how to handle access tokens and refresh tokens properly in the client which means the front end code that runs in the browser and we'll do that with react today before we start writing code it's going to help if you have a basic understanding of what jwts are and what we're trying to achieve so here's a quick slideshow about that from my node.js for beginners course let's discuss the jwt strategy we're going to implement today so what are jwts what is the acronym jwt and abbreviation 4. concerning our node and express rest api jwt is an abbreviation for json web tokens jwts can be considered to be a form of user identification that is issued after the initial user authentication takes place when a user completes their login process and they are authenticated our rest api will issue the client application an access token and a refresh token the access token is given a short time before it expires for example 5 to 15 minutes and the refresh token is given a longer duration before it expires possibly several hours a day or even days while no security measures are perfect we do want to consider the risks of cross-site scripting and cross-site request forgery i will provide links about both in the description below our api will send and receive access tokens as json data to avoid the previously mentioned risks it is recommended for front-end client applications to only store access tokens in memory so they will be automatically lost when the app is closed they should not be stored in local storage or in a cookie essentially if you can store it somewhere with javascript a hacker can also retrieve it with javascript just keep access tokens in memory which you might also refer to as the current application state our api will issue refresh tokens in an http only cookie this type of cookie is not accessible 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 essentially grants indefinite access if a refresh token falls into the wrong hands so the overall access token process involves issuing an access token during user authorization the user's application can then access our rest api's protected routes with the access token until it expires our api will verify the access token with middleware every time the access token is used to make a request when the access token does expire the user's application will need to send their refresh token to our api's refresh endpoint to get a new access token of course the refresh token is also issued during user authorization our rest api's refresh endpoint will verify the token and cross-reference the refresh token in our database too storing a reference to the refresh token in the database will allow refresh tokens to be terminated early if the user decides to log out and again refresh tokens need to be allowed to expire so indefinite access cannot be gained today we're going to apply this jwt authentication strategy to our react app user login so let's go to visual studio code i'll close dev tools resize the browser here's visual studio code now we're starting out in the package json and this starter code is the code from the react protected routes tutorial that was the previous tutorial in this series and you can get this in the description below now with the package json what we want to do is open up a terminal and you'll want to run npm update with this code or you may need to install everything with npm install but if you already have it you can run npm update and the reason i want to emphasize that is just because i've got an update to axios here just since the last tutorial so i thought i would highlight you could update everything and get the latest versions just like i have here in the package json as of the time i'm making this tutorial of course you may be watching this later on there could be updates since i've made the tutorial okay we're going to start out in the source directory and then in the components directory and we want to make a new users component so users.js inside of here you could try typing in your file it doesn't work for me so i do control alt and the letter r because i'm using react es7 snippets and i used to be able to type underscore rafce this is no longer working for me either i just need to type raf ce and it still works it creates a functional component of course you could type all of this out what we don't need here is the import react from react so i'll just eliminate that this does create the user's component but i do need to import some things at the top so i'm going to import and bring in use state right away there we go and after that we'll start inside the component and i'm going to define the state so i'll say const and i'll have users and set users and then we'll set this equal to use state and then i'm going to change the jsx so instead of these divs that come by default we'll add in a parentheses and then we'll start out with an article element after the article element we'll have an h2 and inside the h2 we'll say users list and now after our h2 i'm going to ahead and put a little bit of logic based on the user state so we'll say users we use optional chain and check the length and then we'll start a ternary statement so i'll get an extra line underneath the curly brace here put in the ternary operator and another parentheses now if true we're going to create an unordered list and list out the items that are in the user state and we're only going to list the user's name so we'll map over the user state and then we'll look at not only each user but we'll also look at the index of the array you could import a package like uuid and get a key for each item that way this is just a basic list so i'm going to use the index of the user state and now let's create our li and we'll give it a key and that key needs to be that index that i was just talking about and then inside the list item we'll put just the user name so we'll say user optional chaining again and then chain username of course in that with the curly brace i'm going to press alt z so the code wraps and then we still need to put what happens if it is false instead of true so now we have our colon and our false case here this will just be a paragraph element and inside we'll say no users to display okay if i scroll down i'll make sure we have the export down here and we do so that's good let's save that and now let's scroll back up and look at putting some basic logic in here to get the user's state and then we can display it so we'll also need use effect and then we need to bring in the axios instance that we have and that is up here in the api directory i'll quickly click on that and you can see we're creating a default instance of axios has a base url here of the backend server that we're running from the node.js tutorial and that of course is running here just on localhost now as well so let's go back to users and now we'll import that axios instance so we'll say axios and from and then we need to go up and then into the api directory and then choose axios okay now we can create our use effect here inside the component and we only want it to run when the component loads so it's going to have an empty dependency array and we can put that right at the end and then inside use effect we'll start out by creating a variable named is mounted and i'm going to use the let keyword there and we'll set this to true and then we're also going to define a controller and we'll set this equal to new abort controller and this is something that axios recently started supporting it used to use cancellation tokens but now the abort controller is something that was already used in vanilla javascript and now axio supports that too what it's for is to cancel our request and we'll do that if the component unmounts so we want to be able to cancel any pending request that is still out there if the component unmounts now let's go ahead well i don't want the try block yet i need to actually define a get users function and this will be async and now inside this function is where i want the try block and of course a try block can end with a catch block where we have an error and inside the error block for now we'll just say console error and pass in the error in the try block we'll start using axios right away so we'll have const response equals await then we'll have axios dot get and we'll pass in the user's endpoint then we'll have an option here and this is for a signal and we'll pass in that controller signal that will allow us to cancel the request if we need to i'm going to scroll up just a little bit and underneath this we could go ahead and log this response if we want to and the response from axios is in the data property so it's response dot data and then we'll check our is mounted and this is essentially saying if is mounted is true then do something and we're going to set the user state with that data that we get back okay so this just defined the function after we've defined the function we actually need to call it into action here so we'll call git users but we're not quite finished with the use effect yet let's use the cleanup function of use effect and inside this cleanup function we need to set the is mounted value to false and then we also need to use that controller and call abort and the cleanup function of course runs as the component unmounts so this is what we would want so we're not going to attempt to set the state we would check this and see it's false so we won't attempt to set users which is state that's stored inside the component likewise we'll abort any requests or cancel any requests that we have pending when the component unmounts so you want to do both of these things in the cleanup function of use effect okay and with that we are finished with the user's component for now and i say for now because we're going to have to apply that jwt strategy and come back but i want to show the user's component working and then also of course when it fails so let's come up to the admin component and let's import that user's component and now that we've imported it we can just replace this paragraph here with the users component and i'll put another break underneath and we can save now let's go ahead and start our new version of the application and see what we've got okay the applications loaded i'm going to expand chrome i'll close out of the example application i'm also going to open dev tools so we can see some things logged to the console over here now i'm going to log in as jane who is an admin user and will be able to see the user's component inside the admin component so we've logged in jane got an access token right away that we see over here and it ends in qpu and so now we'll go to the admin page and we got a 401 right away so why did we get the 401 well we're not sending this token so it's going to fail until we send the token and that verifies but we don't want to implement this in our component we want to implement it so we can use a private version of axios and use those tokens with it anytime we use that instance of axios so let's go back to visual studio code and start implementing all of that so we've resized chrome we'll go back to vs code and now the next thing that we need to do is go to our hooks directory and we're going to create a use refresh token hook first so it's use refresh token dot js once again i'm going to use the raf ce to quickly start some of the code and then the import at the top will get changed and here we're going to import axios from our api directory and then axios and then we're also going to import use auth which is a hook we created in a previous tutorial and we're already in the hooks directory so now it's just right here for use auth okay we've got both of those the first thing we'll do inside our use refresh token hook is to pull in the set auth from use auth and now we need to create a function i'll just call it refresh this will also be an async function and inside refresh we'll use axios right away so we'll say cost response equals await axios.get and we're going to go to the refresh endpoint and now we'll have an object again and here we'll set with credentials to true this is very important because this is the setting that allows us to send cookies with our request and this request is going to send along our cookie that has the response token it's a secure cookie that we never see inside of our javascript code but axios can send it to the backend endpoint that we need it to after that we're going to go ahead and set the off state and we're going to use the previous function so we get the previous state here and then inside set off i'm going to create more than one line so i'm going to say console.log and i'm going to use json stringify and we're going to look at that previous state just so we can compare and then we're also going to say console.log and we're going to look at the response.data.access token that we get back from this endpoint after our refresh token is verified we should get a new access token and then we want to return here in the set off our previous state and then we want to overwrite the access token with the new access token and with that we are not quite finished because this function also needs to return this new access token so we can use it with our request because we will call this function when our initial request fails when our access token is expired then it will refresh get a new token and we will retry the function now again here we've just defined the refresh function we have not used it yet but we don't want to call it here we actually want to return it from this hook so we're going to return refresh and then we should have an export default down here and we do so that is our use refresh token hook now to test this hook out we can go back into our users component and import it we won't want to keep it here permanently this is just to test it out so we'll say import use refresh token and now we've pulled it in from the hooks then we can use it here so let's define our refresh function from use refresh token and now we can create a button inside of our jsx and we'll just see what happens when we click it so now underneath we'll add a button element and we'll give this button element an on click inside the on click we'll have an anonymous function and inside the anonymous function we'll call the refresh function so let's just put refresh as the text on the button for now and we'll save it and as a final touch here it might even look better if we put a break underneath it so there we go save that now let's go back and look at this inside of our application again so now we have a refresh button here i'll expand chrome and open up devtools again let's clear out all of the errors but now let's hit this refresh and see what we get okay so here was the previous state and we had an access token that ended in sqpu now we have an access token that ends in seven h u zero so you can see we logged the previous state and now our new state is going to have this new access token that's all we wanted to verify that it was working we can hit refresh again and we'll get the same result so here is our old token now 7 h u 0 and the new token of course is different now that we've confirmed that the refresh is working let's go ahead and close dev tools again resize chrome come back into visual studio code and i'm going to pull this button out we just wanted to use it to make sure that was working but we don't need to keep it there scroll back up to the top go ahead and remove the rest of the code we just put into the user's component for testing and save this now let's go back to the file tree back up to our axios js file it's inside the api directory and we can make some changes here we're going to highlight the export default axios create where we're creating our default instance and i'm going to press shift alt and the down arrow to copy all of that down and now i'm going to change the code just a little where it says export default will be export const i'm going to define axios private and set that equal to an axios create and now i want to create a base url at the top so i'm not repeating myself in each one of these and we'll set this equal to this base url value and then we'll take this base url constant and we'll put it right here inside of each one of these so now they're identical and we say why do we want two of these well we're going to attach something called interceptors to this axios private that will actually attach the jwt tokens for us and even retry when we get a failure the first time that failure will come back with a status of a 403 which is forbidden so right now we have two identical axios instances but what we're going to do is attach something to this axios private that axios calls interceptors those interceptors are going to work with our jwt tokens to refresh the token if our initial request is denied due to an expired token and so this will all work in the background and it won't impact the users inside of the app they won't see what's happening but it will keep everything secure and will continue to rush refresh those tokens on a set schedule now we can add a couple of extra values here to our axios private so let's go ahead and do that let's set the headers and inside headers we'll have an object and then we'll have a content dash type and we'll set that equal to application slash json and after we've set that value it's not a semicolon we want a comma there and then we'll have our with credentials setting again and we want to set that to true and we can save now these will all be attached to every request that is from the axios private instance and with that complete let's go back to the hooks directory now and create a hook that attaches those axios interceptors so inside the hooks directory i'm going to create use axios private.js now this isn't a use axios hook that abstracts everything and you pass in a large object with all the values i've seen many tutorials and articles on that that's not what this is this hook will just be to attach the interceptors to this axios instance so we need to start by importing the axios private that we just created and after we do that we're going to import use effect from react after that we also need that use refresh token hook that we created and then we also need the use auth hook that we created okay now we'll define our use axios private hook inside use axios private we're going to define that refresh function first that we get from use refresh token i think it just tried to put use ref in my imports we do not need use ref up here so if that happens to you you'll know what's going on and then we'll have const auth that we pull in from our use off hook okay skipping to the end we're eventually going to return our axios private instance and that's what we would want there at the bottom besides our default export statement so export default use axios private so you can see this hook is just going to return that axios private instance but by the time it's finished we will have attached the interceptors to the request and response of the axios instance so let's make some room here and we will start our use effect hook and now we'll have a dependency array and we are going to use both the auth state and the refresh function inside of this use effect so we can pass those into the dependency array let's scroll up for some more room now interceptors you could think of these much like vanilla javascript event listeners so they get attached but we'll also need to remove them because if not you could attach more and more and we wouldn't want that it would create a mess with our requests and responses but we'll start out in a backwards order with the response interceptor first so i'll say cost response intercept and let's set this equal to axios private dot interceptors dot response dot use and i'm going to press alt z to wrap the code but i'm also going to press control b to hide the file tree so we get some more room okay now that we have got our axios.interceptors.com what we'll do is get the response and if the response is good we'll just return the response but otherwise we'll have an async error handler here so this is if for example our token has expired we have an access token with a short time span or short life span and if it's expired we will be in this async error handler then the first thing we want to do is get the previous request and we get that from axios by accessing the config property and i'm using optional chaining just to be safe there if for some reason the config wouldn't exist but here we're getting the previous request and so now we can put an if statement and we can check the error response status and we expect it to be a 403 which is forbidden if our request is failed due to an expired access token and then we're also going to check a private property on the request that we'll set called sent and this is saying if sent does not exist or if it is not true essentially and that is because we don't want to get in this endless loop that could happen of 403 so we only want to retry once and the sent property indicates that so here we'll set previous request dot sent equal to true as the first thing we do inside this if statement the next thing we'll do is get a new access token by calling await and our refresh function so now we'll get that new access token returned we already saw that the refresh function was working as we planned and then we'll access this previous request and we'll get into the headers and then we're going to get into the authorization setting and this is where we set the token so let's set this equal to and we'll put a backtick here it starts with the word bearer and then a space and then we'll pass in our new access token and now that we've got the new access token in there all we need to do is return and call axios private again and pass in this previous request so we've updated the request with our refresh token so we should have a new access token and now we're making the request again now as i mentioned these interceptors don't just remove themselves they could pile on and we could have many response interceptors if we didn't remove them so i'm scrolling up a little and we'll use this cleanup function inside of use effect which is perfect for removing them and so now we'll say axios private dot interceptors dot response dot eject and here i actually need the name of the interceptor which is why we set it here and we'll pass it into the eject and that will remove it when the cleanup function runs and that's exactly what we need so now let's scroll up and i said i went in reverse order because we attached the response interceptor first everything should be okay but if not the access token fails and it ends up in the response so that's why i wanted to do that first and now we know we have possibly set a new access token in that authorization header so now that we're dealing with the request we'll define request intercept and this is similar i'll set it equal to axios private.interceptors.request.use and request starts with the config and now inside the function here we're going to check the config and we'll say if the config.headers and we're checking that authorization that could have possibly been set so we're saying if the authorization header does not exist then we know it's not a retry this will be the first attempt so i once again want to access this config headers authorization so i'll just copy that and paste it down and i'm going to set that equal to once again a template literal that starts with bearer and a space but here we'll get the actual auth state and grab the access token out of our state so this could be the access token we were given initially when we signed in or it could be the access token that we got after a refresh either way but this is the initial request we know that the authorization header was not set so we're passing that in otherwise if it is set we know it's a retry and it's already been set down here after a 403 after a failed request so i'm going to go ahead and put a semicolon there before i forget and then underneath this we just want to return the config now we do need to handle an error here as well if it would occur so if we have an error we just pass that over with an anonymous function and that goes to a promise dot reject and inside here we pass the error and that should finish our request intercept and now that i think about it in the response intercept i don't think we did anything yes if this if statement of course was not true we still need something here so this also needs to have a return promise dot reject and an error here so apologies for not adding that to the response interceptor before because if for some reason this part was not true and it skipped this if block then we would still need to return the error whatever error was received and the only thing that remains is to go ahead and add the response and eject that as well i said response actually add the request here so this is request and then we eject the request intercept here as well so we don't want those to build up either and with that i believe our use axios hook is complete let me just check everything top to bottom it's looking all right and we have the export at the bottom yes use axios private the hook is going to return the axios private instance and it will have the interceptors added to handle the jwt tokens that we need to request our data and possibly retry and get a new access token if necessary so let's go back to our users component and we need to change something here we are not going to use axios the default from the axios file in our api directory anymore so let's remove that and instead we're going to import use axios private from the hooks directory so underneath our use state we'll go ahead and define axios private and we'll set this equal to use axios private and then we'll take our axios private instance and we'll replace the axios right here inside of our get users function and now that we've done that everything should be good to go so let's go ahead and test our application i'll resize visual studio code and we'll go ahead and resize everything here inside of the app as well it looks like i need to remove those list item bullets but other than that we're getting our users list let's open up the console and we can see everything that has happened here so we use the refresh token i can actually clear that out let's go back to home and sign out and take everything from the start so we've got jane signing in her refresh token is going to expire in 10 seconds so it doesn't last too long here we've got an hpk at the end of the token go to the admin there we've got the three users that come back in the users array let's go to admin again and it's probably expired already and yes it did so we got a 403 forbidden response from the back end api and then we used the refresh token here is the previous state and it had a token that ended in hpk but then jane got a new access token that ends in 7rg and you can see she can still see the users list and i've talked long enough if i go back that probably happened again so now 7rg was the old token in the state and the new token ends in bfk and she can still see the users list so this doesn't impact jane directly she never sees it happen inside of the app however in the background anytime her access token expires the refresh token that's stored inside that secure cookie is being sent back to the refresh endpoint by axios a new access token is received and that is used to complete the request for the user's data and we can see that happen again here so 403 old token new token and everything's good so i'll close this again but there's something worth discussing what happens of course if the refresh token has expired the one that's stored in the cookie well then you actually want the users to have to re-authenticate but you want to do it in the the least annoying way possible they will get kicked back out of course to log back in but then you don't want to just dump them off at the home page so we want to handle that with react router that we already implemented with our protected routes so at the top of our users component let's go ahead and import a couple of things from react router we'll get use navigate and we'll also get use location from react router so we can use both of those inside now we'll define navigate set it equal to use navigate and we'll also define location we'll set that equal to use location and we can say okay now let's scroll back down where we're catching an error inside of our get users function and here we can put in navigate and we're going to navigate back to the login screen but then in the second parameter here we can set some state that will help us send the user back to where they were instead of getting dumped back at just the home page after a new login so we'll say state and now we'll set a from property inside of state and that's where we pass the location and then we'll of course come out here and set another replace equal to true now we have done this in the previous tutorial as well when we were using protected routes and what this does is take the location that they are coming from say the admin component and they got set to sent to the login but they're going to replace the login in the browser history with the location that they were so then after they log in our login component sends them directly back to where they were and that code was already added in a previous tutorial but we can look at it quickly and of course you'll get all of this in the source code so what we've got here is defining from and we're getting this location.state.from of course if it doesn't exist then they just go to the home page but we get all of that and then in the handle submit here when they log back in navigate is also used here and they're sent back to wherever they came from so i'm going to close out of the login and we'll make sure we've saved our users component and now what i need to do is go to the backend api that i have running here in another instance of visual studio code and you can see we are setting an access token to 10 seconds and that's why it was expiring and in the refresh token controller when they got a new access token it was also just set to 10 seconds but in the auth controller where the cookie is also set we're sending a refresh token it was set to expire in one hour so i'm going to change that to just 15 short seconds so now that will also fail and jane will get booted back to the login screen so what we need to do now is resize visual studio code come back to our app sign jane out and we'll have her log back in i'll clear out the console so we can see the process and jane's access token will expire in 10 seconds she's fine right now she can access the users list multiple times no issue but after 10 seconds there's a 403 here's the old token here's the new token and it's just going to take a few more seconds for that refresh token to expire so when it does kicked back out to the sign in page so normally this would say be after a day maybe if it was a financial site it might just be an hour or something like that but we got two 403s in a row and that's okay because that means our sent property realized that we'd already reattempted the request once and so the second time it went ahead and let it fail so two 403s is what is expected now when jane logs back in she should be taken directly back to the admin component yes she is and she once again has access to the users list so that's how you handle jwts in the front end in react store the access token in your app state don't put it in local storage or in a cookie that can be accessed by javascript and then store the refresh token in a http only secure cookie that is not accessed by javascript but that can be sent back to that refresh endpoint and recognized to get a new access token and if you want to follow up on this i really suggest completing my node.js course for beginners that creates this entire back end that i'm using during this tutorial and this is where you learn about setting these access and refresh tokens and cookies and sending all of those to the front end user 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: 445,456
Rating: undefined out of 5
Keywords: react login authentication, react login authentication jwt, react login authentication axios, react login authentication tutorial, react login and registration authentication, react, react js, reactjs, axios, react login, login, login authentication, react authentication, login auth, react auth, react jwt authentication, react jwt auth, jwt authentication, jwt auth, react refresh token, react access token, tokens, jwt tokens, react cookies, axios jwt, auth, jwt, authentication
Id: nI8PYZNFtac
Channel Id: undefined
Length: 41min 0sec (2460 seconds)
Published: Fri Jan 28 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.