Build an Authentication API with Node.js, TypeScript, Typegoose, ExpressJS & Zod

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this video we're going to build an authentication rest api with node.js typescript type goose and zod the features that the rest api are going to have are you going to be able to register a user you're going to be able to verify a user's email address you're going to be able to send a forgot password email you're going to be able to reset a password get the current user and then you're going to be able to log in create access tokens and refresh tokens so we're going to be using these technologies listed out here and to follow along you're going to need postman node.js and running instance of mongodb if you don't have any of these this is a good time to pause the video and go download and install these applications and this is the video structure that we're going to be following we're going to do a demo and then we're going to do a code walkthrough and then we're going to get started building out the application so if you would like to test out the application before you start building it or you want to follow along there will be a link to the github repository in the description below and in that repository you will find a postman collection json file so you can import this json file into postman and you'll have all the requests needed to test out the api so once you've imported the collection you're going to have an auth api and then you're going to have a user folder and an auth folder so inside a user you're going to be able to register a user so before you send any request make sure you have an environment set in the top right hand corner of postman so i'm going to register a user and then once i've registered a user it's going to send an email and we're going to get a preview link here for the email so i can open up this preview and inside of the email we're going to get a verification code and we're going to get an id so i'm going to copy this verification code here now come back to postman and we have this verify request here and inside the verification code i'm going to replace what i currently have in there and then i'm going to set the id as well and you can see that i've successfully verified this user so we can also reset a user's password and this is going to request an email be sent to that user and then once we send that email we will get another preview link inside of the console here so we can open this up and you can see just like when you register a user you get a password reset code and you get the id of the user so i can copy this password reset code and i can come back into postman and i can replace what i have here and i can also grab the user's id we can reset the password you can see here that i'm using environment variables so i have password two set here and password two is just going to be password123 while password is just going to be password so let's reset this user's password now we can log in as this user and you can see that we're going to get an access token and a refresh token and there's a test script here that's going to set environment variables for the access token and refresh token once you successfully log in so now that we have a valid access token we can get the current user and if you have a valid refresh token you can use that token to refresh your access token so we can send this request here to refresh the session and you can see that we get a new access token and whenever we send this we're going to get a new access token so inside the repository you'll notice that we have this config folder here and we have a source directory so if we open up the source directory we have everything sorted by layers so i have controllers here and then i have services models and we have some middleware and routes and so our requests are going to come into routes and then the route is going to call a controller and then the controller is going to call a service and then the service is going to call models if you want to you can organize your code by user and auth and session but i prefer to organize my code by these layers here so inside of config we have this default.ts and default.ts just exports an object and this is some default configuration that we're going to be using in our application and then we have this custom environment variables and this is going to tell the config module to get these environment variables from our n file so you can see here for example we have this access token private key and then if we open up our dot n file you can see that we have an environment variable for this token here so these public and private keys are base 64 encoded keys so if you want to generate some new keys there is a link here to this travis stidwell json crypt demo and you can use that to generate some keys and that's a really easy way to do it and then you can base64 encode the keys and then you can put them into your end file so you can see that we have a public and private key for our access token and then we have a second public and private key for our refresh tokens don't use these keys that i have in here because as you can see everybody is going to be able to see these keys and i'm not going to add my end to git ignore i'm going to commit this to the repository so if you use these keys you will get hacked so let's get started building this rest api so i'm in a new vs code window here and i'm going to initialize my application with yarn in it i'm going to be using yarn for this tutorial but you can also use npm i'm just going to press enter for all of these and use the default and this is going to generate a package json next i'm going to initialize typescript and i'm going to use mpx typescript dash dash in it and this is going to create a ts config file for us so during the video there will be a couple of things that we need to change in this ts config file but i'll show you those as we run into those problems so you know exactly why we're changing them the next thing i'm going to do is to install some development dependencies if you clone this repository you'll see this notes file here and this is going to have all the install commands for you so you can just copy and paste these so the next thing i'm going to do is to install express and i'm going to install express at version 5 and this is going to let me pick the version that i want to install so i'm just going to pick the latest which is 5.0.0 alpha 8. and then finally i'm going to install my dependencies so you can see that command just here so i'm going to come into the root of the directory and i'm going to create a new folder and i'm just going to call this src and inside of src i'm going to create an app.ts file and then i'm just going to console.log hello youtube let's save that file and then we can come back into package.json and i'm going to create a scripts and then inside of scripts i'm going to create a dev command and this dev command is what we're going to be using to start up our application so i'm going to say ts node dev and then i'm going to give this a respawn flag and a dash dash transpile only flag and then i'm going to tell it which file i want to start up which is source slash app.tier so i'm going to say slash app.ts so let's type yarn dev and see if this works so you can see we get hello youtube and if we save this file it should restart and print out let's just make sure we're getting the new version and we get hello world so we are good to start the first thing i'm going to do is to require dot end and dot end is going to be used to get our environment variables from our end file so i'm going to say require dot and and then i'm going to call dot config i'm going to import express from express i'm going to import config from config i'm going to initialize express by saying const app equals express and then i'm going to say app.listen and app.listen takes a callback and it also takes a port so to get our port let's say const port equals config.yep and then we want to get a property called port so let's add our port down here and then we can just add a console.log app started at http localhost and then we can add our port here so this isn't going to work yet because we have no config defined so i'm going to create a new folder and this folder needs to be in the root of your directory so i'm going to call this config and then inside of config i'm going to create a new file i'm going to call this default.ts so when you go to deploy your application you also need to create a new file in here and you'll need to call this production config is going to expect a file for each of your environments plus default so if you have an environment for test you can create a new one called test as well so inside the default i'm just going to export default and i'm going to export a object and then i'm going to define a property called port and that's just going to be 3000 let's start this again and you can see here we get app started at localhost port 3000 so this is looking good the next thing we want to do is to create our database connection so i'm going to come into config and i'm going to create a db uri and i'm going to set this to mongodb colon slash localhost 27017 and then i'm just going to call this user api tutorial so inside of source i'm going to create a new folder i'm going to call this utils and inside of utils i'm going to create a new file i'm going to call this connect to db.ts and i'm going to import mongoose from mongoose and i'm going to import config from config i'm going to say async function connect to db and then i'm going to get the db uri from config so i'm going to say const db uri equals config dot get and i can come to our default and i can just copy this name here and then i'm going to say try and then i'm going to catch this error i'm going to say await mongoose dot connect and then we're going to connect to our db uri and if we run into an error i want to exit with a status code of one which just means that we've exited with a failure so now let's export our function as default to export default connect to db and you'll notice that typescript is complaining here because type unknown is not assignable to type string so connect expects a string but db uri is type unknown so config.get you can see here takes a generic so we can define that generic here as a string and then typescript is now going to be happy let's come into our app and we can call connect to db and we can just execute that function and i have a little syntax error here see i'm using array brackets i actually need object brackets and then i also need to get rid of these dangling commas here so we don't know if we've connected to our database so we need to log out whether we've connected or not so come into utils and create a new file and call this logger.ts and then we're going to use pino to create a logger so i'm going to import logger from pino i'm going to import day js from djs and we're just going to use djs to format the date and time then i'm going to import config from config so i'm going to say const level is equal to config dot yeah and then we're going to get a property called log level and log level is going to be a string we need to add log level to our config and i'm just going to make this info then you say const log equals logger and we're going to execute logger and we're going to put in some options as an object i'm going to say transport and then transport is going to mean object and target and then in target i'm going to use pinot gritty and then i'm going to put in our level and then i'm going to add a base option and then i'm going to say pid which is process id and then i'm just going to say false for that i don't want it to print the process id and the next property is our timestamp and then our timestamp is just going to be a function and our function is going to return a string so i'm going to do a comma and then i'm going to say time i'm going to add a full column add another string here and then i'm going to say djs dot format so you can format this log however you like but this is how i like to do it so let's come back into our connect to db and then i'm just going to say log so we need to export log as default so i'm going to set export default log then i'm going to say log info and then i'm just going to say connected to tv and you can see here we get an info log that says connected to db let's come back to app.ts and we can change this console log here to a log dot info as well and then i'm going to move this string down into our info log i'm going to save that and you can see here we get a nicely formatted time and we get an info level log so the next thing i want to do is to configure my routes i'm going to create a new folder inside of source and call this routes and then inside of routes i'm going to create an index dot ts and i'm also going to create a user.routes.ts and i'm also going to create an auth.routes.ts so you don't have to split your routes up like this and you definitely don't have to use authenuser but i'm going to use these files here so if you come back to the readme and if we have a look at our features here you can see that these are all going to be user related routes so registering a user verifying your user's email address sending a forgot password reset password and getting the current user and then for auth we're going to have login and we're also going to have the ability to get a new refresh token so inside of our routes and index file i'm going to import express from express and i'm going to say const router is equal to express.router and then we need to execute that i'm going to say router.get and i'm just going to have a health check endpoint here so there's a health check and then my health check endpoint is just going to respond with a status code of 200. so inside of the callback function here we're going to have a request and we're going to have a response we're not going to use the request so we can just replace that with an underscore to denote that we're not actually going to use that property and then i'm going to say res.send status and i'm going to send a status of 200. so to use our routes here we come back into app.ts now i'm going to import routes from routes and then i'm going to say app.use and i'm just going to pass in our routes and i'm actually going to call this router you can see here that we don't have any exports so come back into the routes and index file and this router that we created with express.router we need to export that as a default so we can export that and then to make sure that our routes are responding we can open up a new terminal here and i can just say curl and then i can say localhost port 3000 health check and this is just going to make a get request and you can see we get back an okay so this means that our application is up and running and responding to requests so let's configure our user and auth routes as well so this is going to be very similar i'm going to import express from express and you definitely don't have to set up your routes like this i'm just showing you a way to set up your routes i'm going to say const router it's equal to express dot router and then again i'm going to export default router so basically all of our routes files are just middleware for express so let's copy this and i can paste this into at all routes and then come back into the index file i can import user from user routes and then i can do the same with all throughouts and then under our health check endpoint i can say router dot use and then we're just going to use our user routes and our auth routes as middleware so the first endpoint that i want to set up is going to be our create user route so i'm going to say router dot dot post and then i'm going to have this listen on slash api users and then we require a handler here so i'm just going to add a callback and then i'm just going to add my request and my response and for now i'm just going to respond with a 200 just so we can start our application back up so let's create a new folder here i'm going to call this controller and then i'm just going to create a new file inside of controller called user.controller.ts and then i'm going to create another folder i'm going to call this service and then inside of service i'm going to create a user.service.ts and then i'm going to create one more folder i'm going to call this model and inside of model you guessed it we're going to create a user.model.org so let's start building out our model and then we can go back to our controller and we can start building out our handler so we're going to use type goose and the reason we're going to use type goose is because from this file here we're going to export a typescript interface as well as our model so i'm going to start off by saying export class user and then user needs a capital u because it's a class i'm going to say at prop and approp is going to come from type goose type goose we can execute that prop and we can say email is going to be a string and then inside of prop i'm going to put my mongoose options so i'm going to say lowercase is true because i want all emails to be lowercase i'm going to say required is true because the user is required to have an email address i'm going to say unique is true because all email addresses registered within the system are required to be unique so you can see here it says experimental support for decorators is a feature that is subject to change in future releases set the experimental decorators option in your ts config file or jsconfig file to remove this warning so it's telling us here to set experimental decorators so we can do that in our ts config file and we can come and find experimental decorators and you can see that it is commented out here let's remove that comment save it and it's set to true go back to our model and you can see that the original error has gone but we have a new error here and then to fix that problem we need to come back into our ts config file and we need to find this property called strict property initialization we can uncomment this and then we need to set this to false come into user model and you can see that this has fixed that error there let's say at prop and i'm going to add a prop called first name and this is going to be a string i'm just going to add one argument here and that's going to be required and we'll fight is going to be true and i'm going to do the same for the last name here and then i'm going to the same for a password and we're also going to have a verification code so i'm going to say error location code and then we're going to have a password reset code as well but we're not going to have any arguments in outside of our prop for password reset code because we don't need to set this until somebody requests that their password be reset so inside of verification code we want to add a default here so i'm going to say default and then our default is going to be a function and then our function is just going to execute nano id so you can see we're using nano id here and then this is imported from nano id so we need one more property on here and this is going to be our verified prop say verified and this is going to be a boolean and we want to default this to false so when somebody first registers their account we're going to set verified to false and then once they verify their account we're going to set this verified to true so we can export our class here as a model with a function from type goose so i'm going to say const user model is equal to get model for class you can see that also comes from type goose and then i'm just going to put my user class in here then you say export defaults user model so there's a few more things we need to do to our user model we need to add some default model options so i'm going to use the decorator that comes from type goose called at model options and this just takes an object of options that you want to apply to your model so i'm going to say schema option and then we want to set timestamps to true and this is going to add a default created and updated date to our model and then we also want to allow mixed so i'm going to say options and then i'm going to say allowed mixed and then we're going to use severity which also comes from type goose and then we're going to call allow and the reason that we're going to do this is because our password reset code here is string but we also want it to be nullable so when somebody resets their password we can take this password reset code and set it back to null so they can't reset their password again with an old password reset code so another issue we have is when a user creates a user document their password is just going to be plain text but we don't want that in our system because we want to be able to create a secure user api so i'm going to use a pre-save hook to hash our password so i'm going to do at pre and this is going to come from type goose you can see here it's exports a pre then we're going to execute that and we're going to say pre save and then pre-save is going to take a callback so we want to put in our user as a generic on pre so you can see if you hover over pre you get unknown here where we actually want to set this to our user class and that's so we're going to get some type validation when we use this i'm going to say if not this dot is modified and then i'm going to pass in our password so this is going to say is the password being modified and if it's not then we're just going to return out of this function but if the password is being modified we want to generate a hash based on the password input so i'm going to say const hash is equal to a weight and then we're going to use argon 2. so i can import argon 2 from i've got 2 so argon 2 is an alternative to something like bcrypt but every time i use bcrypt in a tutorial you guys fill my comment section with comments telling me that argon 2 is more secure than bcrypt like you have a bunch of supercomputers laying around ready to crack bcrypt passwords that's called.hash and then we're going to pass in this dot password so now we're going to have a hashed password here and then we can say this.password is equal to hash so we're just going to replace the password that the user puts in with our new hash here and then we're just going to return so now that we have a hash password we need to be able to verify that the password they give us during login is going to resolve to their actual hashed password so let's create a method on our model and i'm going to call this async validate password so don't get confused by where i'm putting this i'm putting this directly on the class here so you can see we have all of our props and then we have our method here so this takes two arguments the first argument is going to be this and this is going to be document type which comes from type goose and we're going to pass in our user and then the next argument is going to be a candidate password and this is going to be a string and what i mean when i say candidate password is that the password we're going to test against the user's hash password when the user logs in this is the password that they're going to supply to us so we're going to use track hatch so we don't throw any errors in here we're just going to return false if we encounter an error so i'm just going to put an e in here and then i want to log this error and that's just for debugging purposes so i'm going to call log.error and i'm going to pass in e as the first argument and then i'm going to pass in a string that says could not validate password and then we're just going to return false so we're going to return away argon2 dot verify and then we want to verify with this.password which is going to be our hashed password because we replaced the password with a hash up here and then we want to pass in our candidate password as the second argument so this is our user model complete let's come back to our user controller and we're going to start building out our registration handler so let's export an async function called create user handler and this is going to take a request and a response and we're going to be able to type these up with proper types so to do that i'm going to create a new folder and i'm going to call this folder schema and then inside of schema i'm going to create a user.schema and then i want to create a schema that's going to validate our request when we go to create a user so i'm going to import from zod and then from zod i'm going to import object string and type of so let's create a constant called create user schema so i'm going to say export construct create user schema and this is going to equal object and our object is going to have a body property and our body is going to be an object and then we're going to have a first name and our first name is going to be a string and then inside of string we're going to have an object and then we're going to have a required error so this is the error that's going to get thrown if somebody tries to create a user but doesn't provide this required field so this is going to say first name is required let's copy this a couple of times so this is going to be last name i'm going to change the error to last name is required and then we're going to have password and this is going to be password is required but we also want our password to be a minimum of six characters so i'm going to call dot min and then inside of dot min i'm going to pass six because that's how many characters minimum that our password needs to be and then we're just going to have an error here that says password is 2 short should be min 6 chars then we're going to have a password confirmation so when the user registers they're required to give us their password and then they're required to repeat that same password twice so we know that they haven't made any typos say password confirmation and this is going to be a string as well and then we're just going to have a required error here that says password confirmation is required let's have one more property on here it's called email and email is also going to be a string we're going to have a required error it says email is required and then our email is required to be a valid email so we're going to call dot email and then if it's not a valid email we're just going to say not a valid email so zodd can also verify that our password and reset password match so we can end that responsibility off to zod and we don't have to include it in our controller which is really nice so on our body object here let's call dot refund and then inside of refine we take a data property and then we have a callback and we're gonna say data dot password equals data dot confirm password and then we're going to have an object as the next argument and this object is going to have a message and this message is just going to be passwords do not match so this is just going to make sure that data.password equals data. password confirmation and if it doesn't then we're going to supply this message passwords do not match we're also going to supply a path for debugging purposes so this is going to be an array of strings and this is just going to be password confirmation so we're going to know that the password confirmation property doesn't match the actual password so a really cool thing about zod is that we can from this schema here export a typescript interface so i'm going to say export type create user input equals and then we're going to use this type of function up here and then type of is going to take a generic and the generic is going to be type of create user schema so inside of this input you'll see that we have body we don't want the body to be inside of an object we actually just need the body property so you can use the square brackets here and we can get body if we hover over it now you can see we get an interface with these properties here so if we come back to our user controller we can type out our request here so we can say that this is a request and we need to import requests from express and we'll import response as well we'll type out response and then if we hover over request you can see that it is also a generic and the first argument is our params which we don't want the second is our response body which we also do on and then the third is our request body so if we open up our generic arrows here and we say our request params are going to be an empty object our response body is going to be an empty object and then our request body is going to be our create user input so now if we say const body is equal to request dot body and we hover over our body you can see that we should have a first name last name password password confirmation and email in here okay so we've typed this out but we can't at the moment guarantee that this input is actually what we expect it to be we need the user to receive an error if the input it doesn't match the schema so let's create a new folder i'm going to call this middleware and then inside of middleware i'm going to create a new file called validate resource dot ts so validate resource is going to be a curried function that takes our schema that we created with zod and then it's going to take the request and response object when the user makes a request and we're going to validate their request against our schema to make sure it matches let's import some interfaces from express so we need our request our response and a next function next we need an any object interface from zod now we can say const validate resource is equal to a function and this function is going to return another function so in this first function here we're going to provide our schema so i'm going to say schema is of type any zod object and then in this second function here we're going to have our request and our response and next so how this is going to work is we're going to call validate resource with our schema and then from there we're going to return this function here and then this function is going to take a request and response and then we're going to call try and we're going to catch the error and we're going to say schema dot us and then we want to pass our body with request dot body our query with rec dot query and our params with rec dot params so we can type out our request here and our response and our next function so if the schema can be passed we just want to call next so this request is good to go if our schema cannot be passed given the payload we want to return res.status and we have to call res.send status now and we want to set our status to 400 and then we want to send e e dot errors so we can set e to any and that will make typescript happy okay so let's export this validate resource middleware as default export default validate resource and we can come back to our user routes here and then we can call validate resource and then in the first argument here we're going to call our create user schema so now that we've executed this function with our create user schema it's going to return our function that is going to do the parsing of the schema so we can get rid of this callback here as well and we can use our create user handler so i'm going to say create user handle so now when we get to our create user handler we can guarantee that our body is exactly what we expect it to be so with our request body we want to create a user but our controllers don't talk to our database we have services to talk to our database so let's create a service for creating a user so i can say export function create user then i'm just going to return user model dot create and then we're just going to call create with some input and then input is going to come from create user and then we want to use partial which just comes from typescript and it means that we're going to select any properties from a interface and then the interface we want to use is our user interface so if we come back to our user controller here we can say try then we're going to catch this error then i say const user it's equal to await create user and then we want to pass in our body so if we managed to create our user successfully i want to return res dot send and then the status of send is just going to default to 200 i'm gonna say user successfully created so you may notice that i don't check to see if the user exists before i try to create it and that is because on a user model we have a unique constraint on our email address so if we try to create the user and the user already exists by that email address we're going to violate that unique constraint and we're going to throw an error and so we're going to come down to our catch block here so now we just want to say if e dot code is equal to 1 1 0 0 0 which means that a unique constraint has been violated then we want to return res dot status and then we want to return a status of 409 and then we want to send a message that says account already exists and 409 just means that there is a conflict otherwise we want to return res dot status 500 dot send and then we can just send along the error so this looks good but when we create a user we want to send them an email address that has a validation code and their user id so they can verify their email address let's come down to utils and i'm going to create a new file called mailer.ts so i'm going to use emails here but you can use anything you want really you could use push notifications you could use sms if you want but i think email is the most commonly used because it's so simple to set up so i'm just going to use a fake smtp server which stands for simple message transfer protocol but if you want to deploy this application to production you definitely don't want to be using this test smtp server you want to be using a real server that's going to send emails to your users that aren't going to get blocked so before we create our mailer you can see in the console we're getting this error user.emailstype is invalid type is undefined so to fix this error we just need to come into our ts config file and find this emit decorator metadata and we need to uncomment this and set this to true and you can see our application will start back up again now so the first thing we need to do in our mailer is to get some test credentials so i'm going to say function create test spreads and then inside of this function it actually needs to be an async function i'm going to say const spreads equals a weight nodemailer import nodemailer from nodemailer and then we can call dot create test account and then we just need to execute this function and console.log our creds console.log threads and then we somehow need to get this function here to execute so i'm just going to create a send email function so function then email and this also needs to be an async function and then we can call this function inside of our user controller so you can open up the user controller and then we've created our user i'm just going to execute the send email function and i'm going to await send email we also need to export default send email and then we need to import send email from util slash mailer so because we've imported this function here we're going to require this whole file and then this create test creds is going to get executed and now you can see in the console we get some test credentials so i'm going to open up my default config and i'm going to create a new property called smtp and then this is going to be an object and then i'm just going to copy in my user and pass and then i'm going to create a host property and this is going to be the smtp we're not going to use imap or pop3 and then the port is going to be 587 and then secure is going to be false when you deploy this application to production you're not going to be using these credentials here and you're going to be using a proper smtp server and you want to set secure to true let's come back to our mailer and we can start filling in our send email function so before we do that we can get rid of these test creds because we don't need to print out our credentials every time we start up our application so i'm just going to comment that out so you can grab this code in the github repository if you need to and i'm going to say const smtp it's equal to config.get and we obviously need to import config from config and then we're going to get our smtp property so node mailer is going to complain because smtp is unknown so we're going to have to provide a type here so we can use each property on smtp so i'm going to say user is going to be a string pass is going to be a string and if you're wondering where i'm getting this from i'm just copying it from what we put into credentials here so then host is also going to be a string port is going to be a number and secure is going to be a boolean so now down inside of sent email we need to provide a payload and to send an email with node mailer we need to create a transporter the above send email i'm going to say const transporter it's equal to nodemailer dot create transport and then this is going to take a object as the argument and i'm just going to spread smtp and then i'm going to provide another property called auth and then auth is going to have user which is going to smtp dot user and then pass which is smtp dot pass so you could create this transporter inside of the send email function if you like but i wouldn't recommend doing that because every time you execute this function it's going to create a new transporter where we just want to create a new transporter when the application starts up and then we can use that transporter for as long as the application lives now we can call transporter dot send mail and the first argument is going to be our payload and then the second argument is going to be a callback so our callback is going to take two arguments it's going to take an error and it's going to take info and we're going to say if error then we're going to log the error out so i'm going to say log.error and then i'm going to pass in the error as the first argument and then a second argument of a string that's going to say error sending email so if we don't get an error we're going to log a preview url and if we see this preview url we know that the email has been sent successfully and we're going to be able to open that preview and see what the email looks like so i'm going to say log dot info i'm going to say review url and then to generate the preview url i'm going to call nodemail dot get test message url and then we just need to pass in our info prop so to make sure that we don't log this if we get an error up inside of our error we just need to return so you can see here that payload right now is of type any and we don't want that we want to type that payload so if we come into send email you can see that mail options takes a mail.options interface and then you can see that there's lots of options on here but this interface isn't exported but luckily node mailer does export an interface that we can use and it's called send mail options so if we copy that interface and then we can type our payload with send mail options and then you can see that send mail options just references mail.options which is all of our options here so if we come back to our user controller you can see that send email now is telling us that we haven't provided a payload so let's provide that payload so i'm going to say from is test at example.com and you obviously want to make this your email address that you're sending emails from two is going to be user.email subject is going to be please verify your account and then text and text is going to be the body of the email i'm just going to provide a string here and i'm going to say verification code and this is going to be user dot verification code and then i'm going to provide an id and this is just going to be the user's id so i'm going to say user dot underscore id so now if we go over to postman we should see that when we call create user we send an email so if we send this request here you can see that we get an invalid type and expected an object but we received undefined and this is a path body so this means that the body of our request is undefined and this is not what we expect but in express we need to use some middleware to pass the body so if we come back into our app.ts we can say app.use and then we're going to use express dot json but previously you would use something like body parser but you don't need to do that anymore you can use to use express.js and as middleware let's send this request so if we come back to postman you can see that we're still getting this error so the reason that we're getting this error is because of the order that we have our middleware so you can see that we have app.use and then we have router and then we're calling our express.json we need to move this above our router and this is a really important concept in express the order of your middleware matters let's come back to postman we can send this request and you can see that we get user successfully created so we should see a preview link in our logs here let's open that up and you can see that we have a verification code here and we have an id here so we can use this to verify user so let's make our endpoint for verifying our user so i'm going to start with the user controller this time so i'm going to say export async function verify user handler and inside of this function we're going to get a request and a response we can type these with request and response and we also need to make a schema for this handler so let's do that now inside of user schema i'm going to say export const verify user schema equals object so you'll notice a naming pattern here so i call the schema schema and then the type ends with input and they all start with for example with create user they all start with create user and in the controller we have create user and then this is a handler so for verify user we have verify user handler and then we have verify user schema and we're also going to have verify user input so verify user schema we're going to use params and params are going to appear inside of the url and our programs are going to be an object and we're going to have an id which is going to be a string and also going to have a verification code which is going to be a string as well so i can copy this and i'm going to call this verify user input and because it's a type i'm going to use a capital for the name and then we don't want body we want params and i also need to change this to verify user schema let's go back to our controller and we can type out our request here so if we hover over this again you can see that params are the first parameter here so inside of our params let's call verify user input now i can say const id is equal to request dot params dot and i should get my id here essay const v equals rec dot grams dot verification code then i'm going to copy and paste that because i'm lazy next we want to find our user by the id and then we want to check to see if they're already verified so let's type this out find the user by id check to see if they are already verified then we want to check to see if the verification code matches so to find a user by the id we need to create a service i'm going to call this find user by id export function find user by id and it's just going to take an id now id is going to be a string then we're going to return user model dot find by id and we're just going to pass in our id say const user equals weight defined by my user by find user by id and then we're going to pass in our id so next we want to say if not user then we're going to return res dot send chords not verify user and then we will say if user dot verified then we want to return res.send user is already verified otherwise we want to check to see if the user.verification code is equal to the verification code that they put in and if it is then we want to say user.verified it's equal to true then we want to call user dot save and then we want to return res.send user successfully verified so if the verification code is wrong we want to return res dot bend good not verify user and i'm just going to move these comments up here and then we can come back to our user routes and we can create a route for verifying a user so i'm going to say router dot get and in the postman collection actually let me say router dot post so this endpoint here i have a post but it could also be a get if you send the user an email with a link to click to verify their account you probably want to make this a get request so i'm going to make this listen on slash api slash users verify and then i'm going to have the id in the url as a param and then we're also going to have the verification code and we want to use the validate resource middleware to make sure that we have an id and verification code in the url i'm going to say validate resource and then we're going to use the verify user schema and then we're going to call the verify user handler so let's go test this endpoint now and this should work so we're going to call verify user i'm just going to call it with something that we know is wrong for now and yep could not verify user so now that we know it works with invalid credentials let's try with the id and verification token that we got previously you set the id here and then i'm going to set this verification token notice that the text has a full stop so don't include that in the verification code and now we have our user successfully verified so unfortunately i can't zoom in with robo but you'll just have to trust me here that there is a hashed password and the verified property is now set to true so now let's move on to requesting a password reset email so inside of our controller i'm going to say export async function we've got password envelope going to pass in our request and our response and we're also going to create a validation schema for this and this is all going to feel very repetitive and that's a really good thing because it means that your code is really predictable so you say export const or got password schema it's equal to an object and this object is going to have a body which is also going to be an object then we're going to have an email which is a string and then we're going to have a required error and we say email is required and then we're also going to make sure inside of zod that it's a valid email address and the reason we're doing this is because if it's not a valid email address we just want it to throw an error inside of our validate middleware and we're never actually going to get to the database request for emails that are invalid so we're just going to say not a valid email it's good to rely on these sorts of validation schemas where you can and that means that you don't have to program custom logic where it's not really necessary so let's create a new type and i'm going to call this forgot password input and it's going to be type of we've got password schema and then we're going to get the body let's come back over to our user controller and we can type out our request here and remember the body is the third parameter so i'm going to say we've got password input then i say const equals rec dot body and then we're going to get the email out of the body and say const user equals await find by email i'm going to pass in our email address so we need to create this find by email function here so i'm going to come over to user service and i'm just going to copy our find by id and i'm going to change id to email name this property and then i'm going to say find one and then we want to find one by the email so this will work and it's perfectly fine but if we're going to be finding users by this property we want to add an index on the email address and it makes the lookup a little bit quicker so if we come over to the user model we can use an index decorator so i'm going to say at index and this is going to come from type goose and then i want to pass in an object as the argument and i'm just going to say email and then i'm going to provide a property of one so now let's come back to our user controller and then i'm going to import find user by email and now we're going to have our user so we're going to add a couple of tests before we actually send out an email they're going to say if not user then we're going to return res dot send and we don't want people to be able to spam this endpoint to test what users are actually registered with our system so i'm going to say const message and i'm going to make this equal to a string and it's just going to say if a user with that email is registered you will receive a password reset email now if the user doesn't actually exist we just want to send this message and this message is a little bit misleading but it means that people won't be able to spam our system with different email addresses testing to see which ones are actually registered because that would be considered a security flaw in your application for debugging purposes we want to log a message here so i'm just going to say log dot debug and then we want to say user with email and then we can pass in email does not exist so if you're hitting this condition here you're going to get a debug log and you're going to be able to debug your system a little bit easier so now let's say if not user dot verified then we want to return res dot send and we want to say user is not verified so we don't want the user to be able to reset their password or log in if they haven't verified their account yet the business logic around this may be a little bit different for your system but this is generally a pretty common use case so we want to generate a password reset code so i'm going to say const password reset code is equal to and i'm just going to use nano id and i'm going to execute nano id you can see this should have been imported from nano id yep so it's just going to create a random string for us i'm going to say user dot password reset code is equal to our new password reset code and then i'm going to call await user.save so the reason that we can do this is because we've just found the user with this find user via email and we can change any property we want and then we actually get this save method here if we come into find user.email and we call.lean here then we come back to usercontroller we won't be able to call.save because when you call.lean you just get the json object of the actual document so you can see the typescript is complaining here and it's giving us a helpful error message so if you want to be able to use lean sometimes and not other times you want to pass that in as options so if you hover over find one you can see that you get some options and you can just pass those into here now we want to send an email to the user telling them what their password reset code is so i'm going to say await send email and then we're going to say u2 is user.email from it's going to test at example.com again you want to make this your business email that you're sending emails from subject is going to be reset your password and then our text i'm just going to again put in a string here that has password reset code and the user's id you probably want to render some html and send them a nice looking email i'm going to say password reset code i'm going to put in our password reset code that we created up here on line 93 and then i want to say id it's equal to user dot underscore id and the reason we're going to send the id along is because we're going to use that to look up the user now let's say return res.send and we're going to send along our message so if you want to add a debug log we can say log.debug and then we can say password reset email sent to and then we can just pass in the email address okay so we need to hook a router up to this so let's say router dot post and i'm going to say api users so you'll notice that all of our routes inside of the user routes start with slash users and this is going to denote that they're responsible for this user's resource here so if we had a post router for example all of our routes would start with router dot post and then we would have sorry let's use product as an example to not get confused with post the http method would have slash api slash products and the convention is to use plural as the resource and then i'm just going to make this forgot password and we're going to validate resource again and the resource that we're going to validate is the password schema and then we're going to call the forgot password handler so we come over to our postman collection and we can request a reset password when we create a user you can see that we set the email address as an environment variable and then we have this email here so this email is going to match the email of the user that we created we open up and have a look you can see here it says cindy lauren and if we have a look in the database that should match the email of the user that we have on file yeah it does so now let's send this email and we get back if a user with email address is registered you'll receive a password reset email so you can see here we have a preview url and if we open this up you can see we have a password reset code and our user's id now let's create an endpoint so we can use this and reset the user's password this time let's start with the user schema and say export const reset password schema is equal to an object and our object here is going to have both params and a body so i'm going to say params is going to be an object and then we're going to have a body which is also going to be an object so in our params we're going to have an id which is going to be the user's id just going to be a string you can put a message here if you like i'm lazy i'm not going to put one then i'm going to put a password reset code and again that's going to be a string it's quite hard to send this request without these params because if you do then it's not going to match the actual route and so it's not really that important to put a error message here because it's unlikely that these are going to get hit the reason that we do this is more for when we're in the controller and we can type it out with the request generic so for our body we require a password and a password confirmation we also required that up here in this body so let's just copy this and i can paste our password and password confirmation down into our reset password schema and then we also need to make sure they match so let's copy this refine here and on body we can call refine and we can make sure that they match let's come back to our user controller we're going to create a handler for resetting a password so i'm going to say export async function reset password handler we're going to have our request and our response and our request object then we're going to have our params here and then we're going to have our response body and we're going to have our request body so for this schema we're going to do something a little bit different so you can see here we're using our body and params but this schema here has both a body and params so i'm going to copy this down and then i'm going to say reset password input it's going to be a type of reset password schema and then i'm just going to remove the body portion and then when we come over to our user controller for our params we can say reset password input and then we can grab the params from here and then for the body we can say reset password input and we're going to grab the body let's say const and we're going to destructure the request params equals rec.params and we're going to say id and password reset code and then we're going to grab the new password out of the body so i'm going to say const equals rack dot body and then we're going to grab the password out of here we can also type this response out as well then i'll say const user is equal to find user by id which is a service that we created earlier we need to find the user by the id and we need to await this otherwise it's going to return a promise so now we can say if not user or not user dot password reset code or user.password reset code is not equal to our password reset code then we're going to return res dot status and then the status we're going to send is a 400 and then we're going to say send good not reset user password so what are we doing here we're going to check to make sure the user is actually registered then we're going to make sure the user has a reset password code if you remember on the user model we made this nullable so it can either be a string or it can be null if it is now we're just going to return this error or if the password reset code on the document doesn't match what the user gave us we're going to throw this error as well okay now we're going to say user dot reset password code is equal to null so this means that they can't use this reset password code again because this check here on line 124 is going to catch it then we're going to say user.password is equal to password then we're going to call await user.save and then we're going to return res.send successfully updated user password let's just say successfully updated password so you may be wondering why we can just set this password like this and we don't need to hash it and the reason we can do that is because we have a pre-save hook on our user model so when we're going to save our user it's going to say okay we're going to be modifying our password let's swap it out for a hash let's come back to our user routes and say router dot post slash api slash users reset password slash and then i'm going to put the id and then the next parameter that i'm going to use is a password reset code we're going to validate resource i'm going to use the reset password schema then finally we're going to use the reset password handler let's come back over to postman and let's try this out so i'm going to send this here and it says could not reset user password and the reason for that is going to be because the id and reset password code are incorrect let's get these from our email but before we reset the user's password let's just check to see what happens when we put in two passwords that are different you can see here it says passwords do not match which means that our schema is picking this up for us which is really good so now let's send this request again and now we have successfully updated the user's password so before we can get the currently logged in user we also need to create a login route so before we create the login route we're going to do a little bit of preparation so i'm going to create a new file this is going to be called session.model.ps and i'm going to export class session i'm going to use at prop and then this session is going to reference the user it's going to say user and then i'm going to use ref and ref comes from type goose and then inverse out of ref i'm going to use user so because this is a reference and this is the typescript definition here and then this is going to be the mongoose definition we need to set up the reference from mongoose so i'm going to say ref and then ref is going to be a function that returns the user class then we're going to have another prop and this prop is just going to default to true and this is going to be valid and then this is going to be a boolean so if our session becomes invalid because the user tells us that they want to log that session out we can set this to false and then we won't be able to create refresh tokens for that session anymore so say const session model is equal to get model class and then we're going to pass in the session and then we're going to pass in a object of options so before in our user model we used this model options so this is just a different way with type goose to add model options so we can say schema options and then we can say timestamps is true now let's export our session model for default session model the next thing i want to do is to create a utility for signing and verifying jwts so inside of utils i'm going to say jwt dot ts then i'm going to import jwt from json web token i'm going to import config from config and this file is going to export two functions i'm going to say export function cyan jwt and i'm going to export a function called verify jwt okay to sign a jwt we need a payload i'm just going to call this object and this is just going to be of type object we need a key name and our key name is going to be either access token private e or it's going to be refresh token private key so the reason that we're doing this is because we're going to use this sign jwt function to assign both access tokens and refresh tokens and we're going to assign each of those tokens with a different key so we need to know which key we're going to be using to sign the token and finally we want to have some options this is going to be a optional input and say jwt dot sign options or undefined so the first thing we want to do is to get the sign in key so i'm going to say const signing b is equal to config.get and then we want to get this by the key name so inside of our config we're going to have to have keys with the name both access token private key and refresh token private key and if we have a look what we have in our dot n file you can see here that these are base 64 encoded keys so to decode the base 64 we need to wrap config.get in a buffer.from and then buffer.from is going to take our string which is our actual key in base64 format and then we're going to say base 64 and then we're going to say to string and then this is going to be ascii okay the last thing we need to do is to return a signed key so i'm going to say return jdwt dot sign object then we say sign in key and then we want to have some options so if options is defined we want to spread that onto our object here so we can do that with dot dot open up a bracket say end end and then options next we want to set the algorithm to rs 256 and this just means that we're going to be using public and private keys so if this user api is part of a micro microservice architecture you can share your public key around to other services safely and then they can be used to also verify this jwt was signed by this user api so typescript is complaining here because buffy.from wants a string so we can use our string generic and that's going to keep typescript happy so this isn't going to work because we don't have access token private key or refresh inside of our config so let's define those and i'm just going to define them as empty strings and then i want to get them from environment variables so inside of config i'm going to create a new file and this new file is going to be called custom environment variables dot yes and then in here i'm going to export a object as default let's move my config over so we need an access token private key we also need a refresh token private key we also need an access token public key and we need a refresh token public key so what this is going to be doing is it's going to say for this property which matches our default config here use this key to get it out of our environment variables so open up your environment variables and then you can see the key that it sets you can see access token public key here access token public key and then this name here is going to match what you have set in your end file so you can say access token private key i have refresh private key a little bit inconsistent with the name here but that is okay and refresh public key okay so we're going to be able to get our signing key here and then we're going to be able to sign a jwt with that key and then we're going to return it now let's create a verify jwt function so a verify jwt is just going to take a token and this is the jwt that we want to verify and it's going to take a key name so our token is going to be a string and then our key name here is going to be similar to this but we're going to use a public key to verify the token so this is going to be access token public key or refresh token public key so let's copy this code here and we can just call this public b and i'm going to do the same thing we're going to get buffer dot from and we're going to get it from base64 encoded format and then we're going to say try d const decoded is equal to jwt.verify so if jwt.verify can't verify the token because it's signed by a different key or it's expired it's going to throw an error so we need to catch that error and then we want to verify our token and then we want to use our public key to verify the token so if we catch an error let's just return null and if we don't catch an error then we want to return our decoded key so verifying a jwt is a little bit annoying because you don't know what's going to come out the other side and so we can use a generic to tell verify what it should be returning so you can see here it's just going to return a string or a jwt payload but we don't actually know what that is so let's create a generic here with t and then we're going to say as t and then we can say that verify jwt either returns t or it returns null in case that we catch an error okay so we're ready to create our login handler so let's create a schema first i'm going to create a new file inside of schema i'm going to call this user i'm going to call this auth.schema.ts and i'm going to open up my user schema so i can copy and paste so i'm going to copy all my imports so i'm going to import object string and type of then i'm going to say export const create session schema is equal to object and then to create a session we want to pass in a body and the body is also going to be an object and inside of the body we're going to have an email which is going to be a string and it's also going to be an email and we're going to pass in a password and our password is going to be a string and we know from when we create a user that a password has a minimum of six characters so if they pass in a password that is less than six characters we know that it's wrong when we get to the validation stage and there's no need to even go to the controller so let's say min 6 and let me say invalid email or password if their password is wrong again we want to be a little bit vague around the message that we give back to them so if they don't provide an email let's say require error and we can say email is required and then for password we can say required error it's a password is required and then if the email address is invalid we're just going to say invalid email or password again we just want to be quite vague around what errors we're sending back to them so let's export a type i'm going to call this type create session input again our types start with a capital letter and we're going to say equals type of and this is going to be type of create session schema okay so we can come to our controllers and we need to create an auth controller i'm going to say auth.folder.ps so if you were to create another authentication strategy such as google oauth or github oauth or facebook award or saml then you would use your auth controller to handle all that logic you wouldn't use a different controller and you wouldn't use your user controller because this is for updating the user resource where the auth controller is for managing all of the authentication code let's create a function here and i'm going to say export async function create session handler and we're going to have a request object it's going to be of type request and we're going to have a response which is going to be of type response and we need to import these from express import run express and we can type out our request so we're not going to have any params we're not going to have any response body but we're going to have a create session input which vs code doesn't want to import only so i'm going to import this manually and that is because i have it in the body of the function i actually want it in the arguments so i'm going to destructure the body and get the email and password out so i'm going to say const equals rec.body we know the body because we've typed it up here so we could say email and password now we can say const user is equal to a weight find user by email this is a service that we created earlier we can find user by their email address and we're getting error here property email does not exist on type body and that is because in our all schema we haven't exported this as just the body so we can go back in and we can add the square brackets and body now we can say if not user then return res.send and i want to send back a vague message here so in our schema we use this message email a password invalid email and password let's copy that and i'm going to define a constant called message const message it's equal to and i'm just going to use this invalid email or password message and i just want to send that back again because we don't want to tell the user that makes this request which users are registered in our system and which users are not registered so now we can say if not user dot verified then we can return res dot send please verify your email so the next thing we want to do is to verify that the password that user gave us is in fact that user's password so i'm going to say const is valid is equal to a weight and then i'm going to call user dot validate password and the reason that we can call validate password is because on a user model we created this validate password method here and then our validate password method is going to take our candidate password which is the password from our request body and then we can say if not is valid then return res.send message and then the next thing we want to do is to sign a access token and we want to sign a refresh token and then we want to send the tokens so to sign these tokens i want to create some services to do that for us so if we have any other authentication strategies such as oauth inside of our application we can just use these services to sign some tokens and we don't have to repeat our logic over and over again so inside of service i'm going to create a new file and i'll call this auth.service.ts and i'll say export function sign access token and i'm going to say const payload is equal to our user and we're going to get user in through the arguments and our user is going to be document type which comes from type goose and it's going to be of type user this is our user class dot to json so we just want to convert our user to a plain json object next we're going to say const access token is equal to sine jwt and we're going to use our sign jwt utility that we created earlier i'm going to sign it with our payload and then we want to sign up with our access token private key and then we just want to return our access token so let's create another service for signing a refresh token and when we sign our refresh token we're going to also create a session so i'm going to say export async function sign refresh token and we're just going to take in a user id and a user id is going to be a string so i'm going to say const session equals await create session and then we're going to create a session with our user id so let's create this create session service i'm going to say export async function create session and create session as we know is going to take a user id which is going to be of type string and then we're just going to return session model dot create and we're going to create it with our user as a user id so now that we have our session created we just want to sign a refresh token that references this session then when we go to create a new access token given this refresh token we need to then just check that the session is valid and in our database and if it is we can sign a new access token so the const refresh token is equal to sine jwt and then this jwt's payload is going to say session and then we're just going to reference the session id it's going to say session dot underscore id and then the key that we're going to use is the refresh token private key and then we just want to return our refresh token okay let's go back to our auth controller and then we can use these two services we created to create an access and refresh token so i'm going to say const access token is equal to sign access token and i'm going to pass in our user then i'm going to say const refresh token is equal to a weight because this is an async function let's just double check that we didn't create sign access token as an async function otherwise it would return a promise and would need to await it but there's no need for sign access token to be an async function so i'm going to say sign refresh token and then our user id is going to be our user dot underscore id now let's return res dot send and i just want to send along our access token and our refresh token so let's hook this up to our router and we should be able to log in now so i'm going to say router dot post slash api sessions then i'm going to say validate resource say create session schema and then we're going to use create session handler let's go over to postman and we can test this login route and you can see that we get an access token and a refresh token so the login request in the postman collection is going to set the access token and refresh token for you so if we open up the environment variables you can see that it has the access and refresh token set so now we can implement the me handler and this is going to return the currently logged in user so before we make the handler to get the current user we need to make another piece of middleware and this is for deserializing the user so in the middleware folder i'm going to create a new file and i'm going to call this deserialize user.ts so all this piece of middleware is going to do is to get the access token from the authorization header and then decode the access token or verify it and then attach the user to the res.locals property so i'm going to say const deserialize user equals async and then we're going to have a request a response and a next object let's import the types of these from express so we have request response and next function just like we did in the validate resource middleware the next is going to be a next function and again i've put these in the function body but they need to be in the function arguments response is going to be a response and then request is obviously going to be a request so before we forget let's export default d serialize user and then we're going to say const access token equals and if we go back to our postman collection you can see in our auth header we have a bearer token and then we have this access token here and this is our environment variable so if we open up the console and send this request you can see in the request headers we have this authorization header and then we have bearer and a space and then we have the access token so we need to get this token out of the authorization header let's say request dot headers dot authorization and then i'm going to say or an empty string because the authorization header may not be present and we don't want this function to throw an error so we can wrap this and then we need to replace the bearer with an empty string so i'm going to call dot replace so if the authorization header wasn't present then we would be calling dot replace on undefined which is not going to be valid we need to call dot replace on a string and then i'm going to pass in a regex here so i'm going to say slash parrot i'm going to say bearer s and then i'm just going to replace this with an empty string so we have our access token here so we're going to say if not access token then we're just going to return next but if we do have an access token we're going to try to decode it so i'm going to say const decoded equals verify jwt and we're going to get this from our jwt utils and then we're going to pass in the access token and we're going to use the access token public key to verify this token and then we're going to say if decoded then that means that we have a user object and we're going to attach the user to res dot locals dot user equals decoded and then you can call next in here but then you would also need to call next outside of here so we can just remove that next and then we can return out of this function so how do we use this middleware well we want to deserialize the user on every single request and we don't want to have to add it to each route because we might forget some so inside of our app.ts i'm going to call app.use and then i'm going to pass in d serialize user again the positioning of this matters so if we were to do this below our router reds.locals.user wouldn't be accessible inside of our routes so now that we've deserialized the user and we've attached the user to res.locals.user we can go inside of our controller and inside of user controller and i'm going to say export async function get current user handler and then we're going to have a request and a response and response is going to be of type response and request is going to be of type request and then we simply need to return res.send res.locals.user and then we need to create an endpoint for this handler so inside of our user routes i'm going to say router dot get api users slash me and then i'm going to call the get current user handler so now if we send a request to me you can see that we get back our user but there's all sorts of fields on here that we shouldn't be displaying to the user so we have our password reset code and we have a hashed password and we have our verification code so let's go ahead and make sure that we never output these fields to the user so i'm going to come back to the user model and i'm just going to export an array of private fields i'm going to say export const private builds equals an array and this is just going to be an array of fields that we want to be private so our first one is going to be password the next one is going to be underscore underscore v then we have our verification code and we have our reset password code and our verified status we don't want to output that either so when we sign our jwt we want to remove these fields from the user so let's go into our session service and when we call user.2.json we want to emit our private fields so i can import omit from lowdash so i'm going to import from lowdash emit and then i'm going to wrap our user.json in a minute so i'll say commit and then the second argument is going to be private fields so let's go sign a new token with login and then get the current user and you can see here we only have our publicly available fields now so another issue with our tokens is that we haven't set an expiry on them so if we come into our all service when we sign an access token let's say we want this to expire in 15 minutes so because we're passing in some options we can say expires in and then we can just say 15 m which is going to be 15 minutes and when we create a refresh token we can let the refresh token expire in say one year i'm going to say expires in and then one why so i'm going to log in again and i'm going to get the current user and you can see that we now have an expiry on our access token so if our access token expires we want to be able to refresh it and that's where this refresh access token endpoint comes into play so come over to the auth controller and i'm going to export another function that says function refresh access token handler we're going to have a request and we're going to have a response and i'm going to say const refresh token it's equal to get and i'm just going to use get from low dash here and come down to this one and i'm going to pass in the request object and then i'm going to get this from headers dot x refresh so if we go over to postman we have a look at this request in our headers you can see that we have x refresh and this is set to our refresh token so i'm going to decode this token const decoded is equal to verify jwt then we're going to pass in the refresh token and then we're going to pass in the refresh token public key then i'm going to test to see if it has been decoded if not decoded then we're going to return res.status 401 dot send would not refresh access token if we could decode the token and it's a const session equals await find session by id and we need to create this service let's go do that now in our auth.service and say export async function find session by id it's just going to take one argument which is an id and that is going to be a string then i'm going to return session model dot find by id i'm going to pass in the id so find session by id and then we're going to get decoded dot session so when we signed our refresh token you can see that we added session and this is the session id so let's say decoded dot session and you'll notice that typescript doesn't like this but luckily we passed a generic into verify jwt so we can use that now so we can say session is going to be a string and you can see now that typescript knows that decoded has a session string on it so we can say if not session or not session dot valid so if the session has been set to invalid then we're going to return this error message again otherwise we're going to say const user equals find user by id and we're going to cast the user id to a string so we can use the string constructor here and we can say session dot user and then if we don't have a user we're going to send back our error message again so you could define this message in a constant above and that might be quite nice the if not user otherwise we're going to say const access token equals sign access token and we're going to pass in our user object then we're going to say return res dot send and we're just going to send back the access token so typescript is not happy with this and this is a really confusing error but the only issue is that we haven't awaited this so this is going to be a promise instead of an actual object and now typescript is happy so let's come into our auth routes and we're going to say router dot post slash api slash sessions refresh and this is going to be our refresh access token handler let's come back to postman and we can open up our refresh access token request and you can see that we're setting the header here you can see that we get a new access token and we can send it again and again again and we're going to keep getting a new access token so there's one last piece of middleware that might be handy and that is to make sure that the user is logged in so there's some routes that you might want to protect and you want to make sure the user is logged in before they can hit those routes for example the user route where they get the currently logged in user you don't want just anybody to be able to hit this route you want to be able to check that the user is logged in before they get to this handler so let's go into middleware and i'm going to create a new piece of middleware and i'm going to call this piece of middleware require user dot ts and i say const require user equals a function and our function is going to take a request a response and a next function and again we can import these from express and then we can type our parameters here so because we have our d serialized user middleware being called up here and then all of our routes are underneath by the time we get to this piece of middleware which is going to go on our actual route such as here we know that the user is going to be on res.locals i'm going to say const user equals res.locals.user then i'm just going to say if not user then i'm going to say return res dot send status 403 otherwise we're just going to return next and we're going to continue through the flow export default require user now we can use this middleware on our routes so if we come over to our user route and the me request i can say require user and now this route won't get to the get current user handler unless the user has a and valid access token so let's try it out so you can see we get the current user let's go over to our headers and i'll just remove the access token and you can see that we get a 403 forbidden instantly add our access token back and we get the current user so that is how to make a user authentication rest api with node.js typescript type goose and zod if you enjoyed this video please make sure you leave a thumbs up a comment in the comment section below and make sure you subscribe to my channel for future updates thank you for watching and i'll see you in the next video
Info
Channel: TomDoesTech
Views: 43,703
Rating: undefined out of 5
Keywords: typescript, javascript tutorial, nodejs authentication, nodejs tutorial, typegoose, typescript tutorial
Id: qylGaki0JhY
Channel Id: undefined
Length: 119min 47sec (7187 seconds)
Published: Tue Jan 04 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.