Build a Login and Registration User Interface in React.js With JWTs and Refresh Tokens

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi and welcome back to another tutorial where we will be building a user interface for our rest api so if you're following along from the previous videos where we built out the rest api this will look all very familiar to you but if you're not that's okay you can still follow along and i'll give you a repository for you to clone and get started with so what we learn by watching this video you're going to learn how cookies work you're going to learn how to make network requests from a react application you're going to learn some basic next js routing you're going to learn how to do form validation with zod and you're going to learn how to build forms with react hook form so this is the structure the video will take we're going to do some minus server refactoring where we'll add our tokens to cookies and we will add a get current user route then we're going to build out the registration page we're going to build out the login page and we're going to make a network request to get the current logged in user and to do all this we're going to be using next.js swr axios sod and react hook form so we have this repository here and this is from the tutorial where we built out a rest api so i'm going to copy the link for this repository here and i'm going to clone it into a new project copy name so to clone the repository i'm going to type git clone i'm going to paste in the url and i'm going to call this one rest api with ui once the repository has finished cloning i'm going to cd into it and i'm going to open it up into a new vs code window so you can see here we have our server files in the root directory i'm just going to make a new folder and i'm going to call this folder server and then i'm going to move everything into the server folder i'm going to open up a new terminal and a cd into server and i'm going to type yarn to install all the dependencies for our server so content once our server dependencies have finished installing i'm going to type yarn dev and remember you need a running instance of mongodb for the server to start for this so once you have your server up and running i'm going to split my terminal here and i'm going to make sure i'm in the root directory i'm going to type yarn create next app and i'm just going to call my next app ui and then i'm going to give it a flag of dash dash type script and this is going to bootstrap a new next app with typescript if you don't want to use typescript just leave out the dash dash typescript and you can still follow along once the app has finished bootstrapping you can use these commands here so we're going to use yarndev i'm going to cd into ui and i'm going to type yarndev and this is going to start our app on port 3000 so we can come back to our browser and we can go to localhost port 3000 you can see that our next.js app loads up let's come back to vs code and i'm just going to stop that server and i'm going to open up the ui folder inside of pages i'm going to remove this api directory here because we're not going to be using that and i'm going to install some dependencies so i'm going to yarn add axios and axios is going to be used to make our network request we're going to use swr react hook form which is going to be used for creating our forms zod that we're going to use for validation and hook form resolvers that allow us to use our zod schema in react hook form where those dependencies are installing i'm going to come over to pages and i'm going to create a new folder and i'm going to call this auth and inside of auth i'm going to make two files i'm going to make one called register.tsx and i'm going to make one called login so these are going to be our pages for logging in and registering because we've put them in an auth folder they're going to be localhostport3000 slash auth login and slash register to start our ui up again and let's start building out our registration form i'm going to import from react book form and i'm going to import a hook called use form and i'm just going to create the page i'm going to say function register page and then i'm going to export alt register page and i'm just going to return and i'm going to return a fragment and the first part in our fragment is going to be a form and let's just put one item in the form for now so i'm just going to say input i'm going to give this a type of email and a placeholder of jane.example.com let's come back over to our ui and make sure this page is rendering correctly so go to logo her support 3000 slash auth register and you can see here up in the top left we have this input here so this page is working correctly let's build out the rest of our form so i'm going to wrap all the inputs in a div and i'm going to give this div a class name equals form element and i'm not going to do too much styling on this form because no matter what i do you're going to be doing something different i can guarantee it and i'm going to give this input a label and the label is just going to say email and the way that we attach the label to the input is i give this a html4 attribute and i say it's for email and then i give the email input an id of email so let's initialize our form so i'm going to say const and we're going to get some properties out because use form is going to return an object and then i'm going to get out a property called register then inside of that input i'm going to register this input with our form so i'm going to spread the register function and then i'm going to pass in a string that just says email and in news form we're going to get some errors and so the errors are going to be in form state and then you're going to have an errors object and under input if we have an error with our email field we want to print it to the screen so i'm going to add a p tag and then i'm just going to add errors dot email dot message i'm going to add a question mark here because email could possibly be null because the errors object could be empty so if we go into styles and then open up global i have a few styles here that i prepared earlier just to make the form a little bit easier to see so i'm going to split my window here and then i'm going to grab my browser and i'm going to split it over here so we can see it as we go so you can see we have our email input here and if we add a button and we give this button a type equals submit and i'll give it a label of submit we can create a function here for handling the subnet so say on function on submit and onsubmit is going to get some values and for now i'm just going to console.log values and in our form i'm going to say on submit is equal to and we need to get another function out of our use form and this is going to be called handle submit i need to pass hand or submit into on submit and it's a single argument into handle submit i need to pass the on submit function let's open up our console and if we type into email and click submit we should see the value of email appear in the console let's say jane.example.com i'll click submit and we have an object here and if i expand the object we can see values and we have email so our form is working if we go back to our server i'll open up source and schemas and user schema we can see we have this create user schema so we have a name email password and password confirm let's copy this schema and bring it back over to our register form and i'm going to remove the export and we have this body here because in express the payload is going to be in our body object but that's not true for our form here so i'm going to remove body and then i'm going to move this dot refine function to the root of our object up here i'm going to move it down here and we need to import all these things from zod and import from zod and we need to import object and string so we now have a schema for our forms validation let's use it i need to import the zod resolver so i'm going to import an object from at hook form flash resolvers slash zod and it's going to be called zod resolver so the way that we can use this resolver and there's lots of different resolvers for hook forms there's one for yup and all other validation libraries or most validation libraries i should say probably not all and so when we call use form we're going to pass in an object and i'm going to pass in a property called resolver and then resolver is going to be our zod resolver and then we're going to pass in our create user schema let's console.log these errors and we should see some errors appear in the console let's try click submit and we can see that we get some errors here it says name is required password name is required on password that is incorrect and confirm password is required so this name is required here should be that's what is required so let's try remove our email from the input and click submit and it says not a valid email okay a valid email in and our error disappears let's do this for all the other fields so we get a form where that passes validation i'm going to copy this here and we need one for name one for password and one for confirming the password and change this to name change the label to name the type is just going to be text placeholder is going to be jane so and we're going to register a field called name and the email should be errors.name.message for password and change this to password the id is going to password the type is going to be password the field that we're going to register is called password and we're going to get the error out of password and the placeholder let's just give it some stars so the field is going to be called password confirmation so let's paste this they here password id is going to be password confirmation the type is going to be password the placeholder we're just going to pop these stars in and we're going to register a field called password confirmation and we're going to get the errors out of that same field so let's fill in our form so i'm going to say jane.joe at example.com for the name of jane doe password is a password and then i'm just going to say password again then click submit and we can see that we have an empty errors object here so that's what we would expect or this input is valid and if we have a look at values you can see that we have jane doe password and password as the password confirmation so let's add something wrong here so i'm just going to add jane doe as the email and it says not a valid email remove name try submit again and it looks like name is not appearing so the issue with this is that on string we need to call a property called non-empty and then we need to pass our message into non-empty and we need to call it message so i'm just going to fix all of this validation here and now we get some validation on all the fields so let's test our passwords not matching so i'm going to say password and then for confirm password i'm just going to say password one two three click submit and we need to have the other properties filled in and it says passwords do not match so that is because we have this dot refine that's asserting that both the passwords should match so the next thing we need to do is to make a network request so we can actually register a user but before that i'm just going to show you a little trick here you can see that values has an implicit type of any we have this schema up here and a schema is going to match what values are so we can infer what our values are going to be from this schema so from zod i'm going to import type of and if you watch the api tutorial you this will look very familiar to you so i'm going to say type create user input is equal to type of and then typeof is a generic and i'm going to say type of create user schema so now we have an interface here that is going to match our form inputs let's use this type here so use form is also generic so we can pass in our create user input into use form and we can also type our values here so to make the network request i'm going to use axios so i'm going to import axios from axios and in our onsubmit handler i'm going to add a try catch because we want to catch any errors that actually it's encounters i'm going to await our axios call which means that our onsubmit function needs to be an async function i'm going to say axios.post and i'm going to say api users and then i'm going to pass in our values into the payload of this post request so the issue with this is we need to send it to our server so we could hard code the http localhost port 1337 here but that's not going to scale very well because as soon as we deploy this application it's going to break instead let's use an environment variable so in our ui folder i'm going to make a new file and i'm going to call this dot n dot local and then make one new environment variable and it's important in next.js to know the difference between public and private environment variables so we need this environment variable to be public because we need it to be accessible via the browser so i'm going to prefix it with next underscore public and then i'm going to name it so i'm going to call it server endpoint is equal to http host port 13337 now if i stop our user interface server and type yarn down again we should have access to this endpoint i'm going to use our template strings and i'm going to say process dot n dot next public server endpoint and once we catch our error we want to display it on the page so i'm going to import use state from react and i'm going to create a new property called register error is equal to use state and i'm just going to default this to null register error and set register error and if we catch the error i'm just going to set it it's going to be in e dot message and then we can display it on our page i'm just going to display it in a p tag here let's fill in our form and open up our network panel and i'm just going to say jane doe jane doe password password and i'm going to click submit so we're getting some errors here and i'll have a look in the console and this error is saying the same origin policy disallows reading the remote resource at localhost 1337 or's header access control origin is missing so when you see this error it's telling you exactly what the problem is it's expecting a header called access control allow origin and the value of that header should be our browser's url so if have a look in our network request you can see we're sending an options request here and as we would expect the options request doesn't include that header so let's go and fix that i'm going to stop our server and i'm going to type yarn add pause and then i'm going to install causes development dependency so i'm going to say yarn add types flash cause and i'm going to store that as a development dependency so i'm going to open up app.ts and i'm going to import clause from pause and then we're going to use cause as some middleware so i'm going to say app.use and i'm passing cause i'm gonna execute cause and i'm gonna execute it with some options and one of the options that i'm going to execute it with is origin i'm just going to set this origin to config.get and i'm going to set it as origin so we obviously need to put this origin into our config so i'm going to open up config defaults you say origin is equal to http localhost port 3000. so this is a security feature implemented by the browsers that says unless the server explicitly says that you can call this endpoint we're going to block it so this is our server telling in the browser yes we expect requests to come in from this endpoint so i'm also going to pass in another property here and this is going to be credentials and i'm going to set this to true because we're going to pass a header for credentials later on and this is going to tell the browser that we're expecting that header i'm going to restart the server with yarndev i'm going to come back to the browser and i try send this network request again you can see that we get a 204 with no content and if we have a look in access control allow origin we now have a header and this is exactly what we want so this will now send our post request and if we have a look at the response you can see here that we get our user back so this has successfully created our user once we create the user let's just redirect them back to the home page so to redirect back to the home page i'm just going to use next router so i'm going to say import from next router and i'm going to import use router then inside of our page i'm going to say const router equals next router and i'll execute that then when we encounter our error i'm going to say router.push so once we successfully create a user i'm going to say router.push and i'm going to redirect them back to the index page let's make another user here jane dot dot example.com green doe password password and we should get an error here because jane doe is already registered and you can see we get an error here and it's catching okay so let's register a user that doesn't exist i'm just going to say gain at example commit and we're redirected back to the index page before we make our route for logging in i'm just going to go to the server and do a little bit of refactoring so the first thing i'm going to do is to make a handler for getting the currently logged in user let's say export async function get current user and this is going to take a request and a response and there's going to be nothing in the request body so we can remove this generic here and if you remember in previous tutorials we had some middle hair here where we deserialized the user and we had some where we could require the user so if we add the require user middleware we can then assume that the user is on the response object so i'm going to call return res.send res dot locals dot user and this should be our handle at complete let's add a route for this in my routes file i'm going to import my angela from user control then i'm going to add a route here i'm just going to copy this one here i'm going to make it a get request and i'm going to make it api me we want to use this require user middleware and we want to use the get current user handler so if we go to this endpoint now we should expect to see a 403 let's go to http localhost 1337 slash api slash me and yep we get a forbidden error so before we can use this we need to log in when we log in we're going to get a couple of tokens back we're going to get an access token and a refresh token and we can see this in our create user session handler you can see we get an access token and a refresh token so this is all well and good and you could store this inside of local storage if you want and then you could attach it to every single request i personally don't like to do that i like to set my tokens in cookies that way they're all handled for me and i can guarantee that they're always sent back to the server so to do this i'm going to set some cookies here and then we're going to pass those cookies when the user makes a request so before we send the cookies back i'm just going to call res.cookie i'm going to set one for access token so i'm going to call this access token i'm going to pass in some properties for this cookie so i'm going to set max age and max ages in milliseconds and this is the milliseconds for 15 minutes and say http only and this means that the cookie can only be accessed via hdp you cannot access this cookie with javascript and this is a really good security feature that you get with cookies that you don't get with local storage i'm going to set the domain to localhost obviously when you use this in production you want to be able to change this domain so you should probably set this in config i'm going to set the path to slash i'm going to set same site to strict and i'm going to set secure to false so in production you want to set secure to true so you want to have a flag key that says is production then it's equal to true otherwise it's false and this just means that the cookie can only be used over https where we have it set to false because our client is using http so let's copy this here for the refresh token so i'm going to call this cookie here refresh token and i'm going to change its max age because our refresh token is set to one year we're going to set it to this number here which is one year in milliseconds so this all looks very good let's have a look in our middleware and i'll go to deserialize user and you can see here that we're sending a new token back to the user in the headers so we also need to set a new cookie here i'm going to come back to our session controller i'm going to copy this here i've actually missed the body of these cookies here so i have the name here and then i have an object and this object is settings but i'm missing the actual cookie i can put that in there and i can do the same for refresh token that would have been quite a big mistake let's copy this here i'm going to come back to d serialized user and when we set our header this is also going to set another header and this is going to have our access token in it if the client wants to use local storage they can because these headers are set and we're returning the new access tokens so they'll have access to them if they don't want to use cookies otherwise the client can set and use these cookies so the last issue is that we're getting these from their request headers but we actually need to get the cookies from the request cookies so out of the box express does not ask cookies for you you need to install some middleware so i'm going to say yarn add cookie asa and just like cause i'm going to install cookie passes type so i'm going to say yarn add at types cookie faster and i'm going to store that as a development dependency i can start this day over back up again so we have it running so we want to be able to get the access token from the cookie or from the header so i'm going to add another get here and then i'm going to say all so we're going to get this from the request and we're going to get it from cookies dot access token otherwise we're going to get it from the request headers and we're going to do the same with refresh tokens so once we create the login page you'll be able to see this in action so let's go ahead and do that now we'll come back to our ur code and in our auth folder and login this is where we're going to create our login page so i'm going to cheat and i'm just going to copy the contents of the registration page and i'm going to paste this into login and then i'm just going to rename some stuff i'm going to call this login page i'm going to export it as login page we don't need a password confirmation field so we can remove that we do need a password field so we can keep that we don't need name and we do need email so that looks good the route that we're going to hit is slash api slash sessions and we're not going to use create user input we're going to use a different schema so i've come over to our source folder we can find in our schemas a session schema and we have this create session schema so let's copy that and i'm going to replace this gamer here and like we did before i'm going to remove the body portion of the schema and then i'm going to create a input type with it and we can replace these interfaces here we also need to replace the schema in our zod validation resolver our error here we can call this login error and we can call this set login error we can change the paths here so we also need to change our validation here to be non-empty i'm going to change these i'm going to cut out a message from here and paste it into a non-empty and i'm going to do the same for password so this looks good let's come back over to our login page so i'm going to say slash auth slash login and we have a login form here let's say jane.dough.example.com and we created this user with the password of password let's put this in and click submit and then if we have a look at our network requests we can see what happened so we sent an options request and our options request was successful and so we sent our post request with our email and password and in our response we got a access token and a refresh token as we would expect you can see we have some headers here to set a cookie we have access token and we have access token here and then we have refresh token so if we look in the storage section and i'm using firefox if you're using chrome this is actually in a different section in cookies you can see that we have no cookies i also have this extension up here called cookie editor and it also indicates that we have no cookies so figuring out why cookies aren't being set is possibly one of the most annoying errors to try debug and it's something that i've debugged a lot in the past and the only method that i can give you to debug errors like this is to try different options so when we set the cookie try a bunch of different options in there and then when you create the network request to get the cookie try some options in there in this case the problem is that we're not setting with credentials to be true so let's come back to slash auth login and we'll give this another shot and say gene.sample.com password inspect our network requests so with credentials needs to be the last option the first option is the body of the quest and then the last option is the options for the axios request let's try this for a third time i'm going to click submit and you can see here we've made a post request we get our tokens back so let's go make a network request to get the currently logged in user so i'm going to come back to api and i'm just going to make this network request on the index page i'm going to remove the contents of this index page so remove the footer the main and the head and i'm going to import swr from swr and i'm going to create a utility for doing the fetching i'm going to create a new folder and i'll call this utils and inside of utils i'm going to create one called fetcher.ts so swr doesn't actually do any fetching for you you need a fetcher function to do that swr is going to handle the loading and error and data states for you so i'm going to import axios from axios and we're going to use axios to do the actual fetching and say const fetcher is equal to and this is going to take a url parameter which is going to be a string and it's going to return axios.get and we're going to get our url we're going to get it with credentials set to true and this is going to make sure that our cookies are sent along with the request and i'm going to call it then axios is going to return with a response i'm going to say res and then we just want the data out of the response so i'm going to say res.data and this is our fetcher function complete i'm going to export default picture and come back to index and i'm going to use this fetcher function along with swr to get our user so i'm going to say onst and swr is going to return an object let's say equals use swr and this should be called use swr because this is actually a hook and the endpoint that we're going to use is our environment variable that we set before or the server path so i'm going to say process dot n dot next public server endpoint and it's going to be slash api me so this is going to hit the endpoint that we set up earlier i'm going to have data so i'm just going to say please log in and then if there is data so if data then i'm going to assume the user is logged in so i'm just going to return a div that just says welcome and then i'm going to json.stringify the data for now so we can see what the payload is the next thing we need to do is to pass fetcher to use swr so i'm going to add vecher and vs code is not and vs code is going to import that for me let's come back to our index page and i'm going to refresh and we're getting a 403 error here so let's have a look and see if we can find this area in the console and we're sending along an access token so let's try debug this error i'm going to come over to d serialize user i'm going to console.log out our access token and i'm just going to make sure that this is coming to the server correctly and refresh the page and access token is an empty string so before we installed cookie parser but we never actually used it so our access token is obviously just going to be an empty string here let's fix that by using our quickie pass on middleware so i'm going to import wiki passer from wikipasser we're just going to say app.use cookie pasta and it's that simple let's try this again refresh our page and you can see we get an access token here and we have our data on the page here so this looks good i'm just going to remove this console log because that would get quite annoying so we probably just want to log the user's name so let's just say data dot name and you can see here data is type of unknown so we know what our data interface should look like it should look like a user let's create this user interface and then we can use this in swr i can type user and you can see here now data is typed as in it's our user dot name you can see here that typescript is complaining so it's saying promise unknown which is coming from our fetcher it's not assignable to parameter read-only and it's complaining here because we have user so let's make our fetcher a generic function so i'm just going to say t and this is going to return a promise with t and then our get also needs to return t and this will keep typescript happy there's one little issue here if we refresh the page here you can see that it only loads me on the client and we see the please login flash up so we want to render this on the server so to do this in next.js we're going to create a function called getserver sideprop so i'm going to say export async function get server side rops i'm going to say const data equals i'm going to call that fetcher function and then i'm going to paste in the url so this is going to fetch our data on the server and then we can populate use swr with this data then i'm going to return an object and i'm going to say props and then i'm going to say fallback data is equal to data then when we come up to here we're going to have fallback data in our props and then in use swo we can chuck it a third parameter here called fallback data let's clear a network request here refresh the page and it says fallback data is a promise so we need to await this fetcher here let's try this again we're getting a 403 error here when we try fetch the user server side and that's because when we fetch the user server side we're not passing along the headers it's actually the next server that's making the request so let's fix that so i'm going to change this get server-side props to a constant so export const get server-side prox equals async function and then we can type this properly so i can type this as get server-side props and this is imported from next then we're going to get context then our headers are going to be in context dot request dot headers so we need to pass these headers along to our fetcher function so let's pass them in here as a second argument and then we can open up fetcher and we can say headers and we're just going to default this to an empty object and then we can pass headers onto our fetcher function here so i just need to fix my arrow function there let's save this and refresh to try out our get me functions you can see that it loaded up the user and it didn't load the please login text before we saw the user so this has all been fetched server side so before we finish up let's fix the types here for fallback data so i'm going to use the next page generic here and i'm going to say that ballback data is of type user and i just want to show you one problem that can happen so let's clear out our cookies and then i'm going to refresh
Info
Channel: TomDoesTech
Views: 1,621
Rating: undefined out of 5
Keywords: react.js, jwt, react with jwt, refresh token, learn jwt, typescript, react typescript, nextjs, typescript nextjs, learn javascript, javascript
Id: oSz23pPBpFY
Channel Id: undefined
Length: 49min 15sec (2955 seconds)
Published: Tue Oct 19 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.