NestJs JWT - Access Tokens & Refresh Tokens - Ultimate Guide

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

The intro did put me off. Personally I feel that a flowchart diagram would have been a better way to explain the concepts you were trying to express.

I really don't want to hear somebody talking about their new mac being better than their old mac when I'm watching a coding tutorial.. I just want a rational explanation of what is happening, and the code.

When you were trying to get some stuff working and had errors, and did not explain anything regarding the errors.. I could not watch anymore.

👍︎︎ 2 👤︎︎ u/ragingrabbit69 📅︎︎ Dec 06 2021 🗫︎ replies
Captions
hello everyone i am back and i am back with a new very long video about authentication gwt authentication in nsgs so i really want to make a very in-depth video about that subject because i noticed that there's a lot of stuff online but nothing really explains it clearly and nothing really gives you all the right tools to make it work so before we start know that in most cases you should probably use sessions that's what i understood from my research that most of the time sessions is what you need but if you want to use gwt and you understand the downside of it i have uh think i think i have found the right kind of trade-off that will allow you to have all the pretty much all the functionalities you might need for your app so and it's not just a theoretical video it will be also a practical video where we are going to code everything in sgs following the best practices but for now let's get started with some use cases so we can really understand what we are doing so in most videos online i see one thing people explain access tokens they don't really explain refresh tokens and they say like they say something like oh you know you should call a local sign in or sign up um route and then you get an access token and the access token will be like a short-lived it will be something for 15 minutes and and that's fine that's that's fine so basically you sign in you have a token and then you can make requests for 15 minutes uh usually when the 15 minutes are over you need to sign in again this is of course a bad thing so uh so what uh usually people do is that before the end just before the end of like uh or the expiration let's say at the 14th minute they send back the um the uh token they send back a request to get a new token so in that case we have a very simple odd so basically you you put your credentials password and email and you get an access token and you can make api requests this access token will be expired in like let's say 15 minutes that's usually the standard if you want to be able to use your application longer without resigning in you usually have um to implement a refresh route so basically what is happening is that you need to then use a refresh token so the access token will be uh will be shortly for 15 minutes the refresh token is usually for one week right so basically that means that if your user goes away for three days and comes back he can reuse your app without having to resign in um obviously that's uh that's a given for a lot of users and that's something that should work so usually uh what we see is something like that uh when you sign in you receive a token pair so access token and refresh token and that's for instance what a access token might look like you can put any claims that's called a claim in the json that you want but most of the time you will have a sub for the kind of user id usually it's unique id so not an integer it's more like a string like a uuid you have email creation date i think in expiration right and you would have something similar with a refresh token and once you want to refresh your token you want to get a new access token that will be short leave again for the next 15 minutes you provide the refresh token and the server gives you back the access and refresh token pair together so you replace them by the two you have so every time you kind of you know you it's kind of for rent you just have more time every time to to use the app so that's fine but that has uh two main problems the first problem is logout how do you log out the user like gwts are self-contained so every information is there you cannot stop really a user from making requests and even uh yeah how do you even do that and and you have the log out and you have the other thing which is what happens if someone stole steals the refresh token right you need to have a mechanism to actually say okay this user reported that his refresh token has been stolen he has a virus or something so we need to we need to um kind of blacklist it so there are lots of different ways and if you want to do it correctly like buy the book it will it it is very complex that's why i say most of the time you should not use gwds but if you want to use them i have a very simple solution i think that works in most cases the downside is that you will not be able to have an app like spotify i mean you need to create more logic but it kind of goes against it because like sessions do it automatically so if you have a app like spotify and you want to track how many active users at the time there is then sessions is very good otherwise if you just have an app where you have only well you can have only one user at a time for a certain account and well if you don't care really right because gwt don't want once forbid that and if you just want to scale without without any like thoughts too much about it because um if you have uh sessions you will need to read this instance uh just for um sessions but uh if you want to scale like between servers but if you have a gwt you still might need to read this but for caching right so if you want to scale without thinking too much about your app gwts could work then i think it will depend of what you want to do exactly but for here now with wt's let's go ahead so this example we have access and refresh tokens so every time you want to get a new access token you provide a refresh token and the server gives you back so if you want to log out now it's a bit complicated let me zoom in out out and you basically sign in the server gives you the access token and the refresh token pair but at the same time it saves the hash of the refresh token in the database so and upon next request like say it's a protected route so like a refresh you provide the refresh token and the server will say let me compare the hash of that refresh token to the hash i have in the database and if it's correct uh the the refresh token is not blacklisted and you can you can get a new pair if it's blacklisted that means it's either stolen or you have logged out that you cannot like you need to go through the sign in again right that's the whole system and if you want to log out it's quite simple you we just delete the hash of the refresh token from the database if it sounds a bit theoretical don't worry because in a minute or two we're going to jump into code and code that and i'm going to explain everything so hopefully it's much clearer and also like the case is what if a refresh token is stolen one you connect to the client database and you manually delete the refresh uh token hash from the database and that should kind of log out all the users obviously users will have access from their like until their access token is valid it's like um for instance something like 15 minutes uh let's say a user log has locked out well his access token would still be valid for the remaining time but that's the concept of gwts you can't really do anything about them and trying to fix that will kind of go against the whole concept of gwts and it's better to use sessions in that case okay so now now now now let's jump into code i hope that this session this intro was not too too messy and let's go let's jump into code and see how we can code that thing okay okay okay i hope that this intro didn't put you off i hope that you didn't drop before because this is the most interesting part this is where we're going to make the magic happen so i have a couple of tools with me first of all i have my new mac so oh my god i'm so happy because i had so much problems with the old mac it made so much noise when i was recording now i can literally record everything and it doesn't even make an sound and second i have a cup of coffee so that's why i'm a bit excited about the subject let's jump straight in i have i have the project here uh that i generated it's just a um a nest default project with yarn so you can create it nest new and then uh you name it the way uh sorry nest new and you name it the way you want i named it nesgus gwts the whole source code will be available on github in the description of this video below so yeah feel free to download it and play around with it you know when you learn code when you learn a concept the best way to learn is to really go deep in in the stuff and try to understand the concept not just trying to use them but trying to understand it as a programmer i am i noticed a huge improvement when i started to do this simple thing for instance here you have a main ts file that you're probably familiar with if you have worked with an sgs right like one of the things i now do every time like every time i don't know something for instance this nest factory well boom i i go there and i check what it is you know and i see that it's a constant and it accepts factory settings and i basically check everything i try to take to check that documentation because that documentation is the best documentation ever like the documentation that you will get at netcs it's good but a lot of things might be outdated or not explained here everything is explained you cannot lie with the code so let's go ahead and and start first of all we have this bootstrap script um i will put it at um at port 3333 because usually i'm using react for 3000. i'm going to delete this spec file here i don't need it in a real development i actually use a lot of specs files i use a lot of integration tests maybe if you want we can do a video about that in the next session because oh my god i discovered a really nice way of testing the app and it that accelerates my development so much and it's just a pleasure and i'm going to share it in the next video uh if you guys want so uh controller we don't need and service we don't need because we only need one module and that's it so let's uh save it we don't need any providers and that's about it let's go ahead i have my terminal here and gwds oops and let's start it to see if we didn't forget anything okay that seems to start correctly okay now um the stack i'm going to use for this app i'm going to use prisma i started to use prisma for a while and i think it's it's just amazing it's just amazing i was using typorem before prisma makes my life easier so i'm going to go ahead and install prisma yarn add prisma latest and yarn add at i think prisma clients something like that right yup so i think we can do npx prisma prisma or prisma init prisma init prisma init so it generates the the initial prisma folder and cool that's all i need now we have a prisma schema and it just says that oh prisma schema client provider postgres ql i'm using postgres for for the for the stack by the way so that's cool and i'm just going to declare a model and the bottle will be basically user user so we have an id and which is an id and default is auto increment basically it says prisma that it's a automatic incrementable id we have a created ad updated that field we also need to have an email field so that is going to be a string is we have uh the hash so the heart that will store the user password for authentication uh it will also be a string and hashed rt and this will be a optional string the hash heart rt will store the hash of the refresh token refresh token is rt and usually i do ac for access token okay that's cool now we have created the model and we need to generate the migration that we're going to apply to the database so to generate the migration i just say [Music] let's see if if it can generate it i doubt that it will be able to do it because it needs to to have a database but we're going to go ahead and create a database soon migrate dev create only i hope it will just okay it cannot it can't really make them but it's okay it's fine we are going to um we need to create a database first so here prisma created a dot n file for us and basically it will inject that database url into into prisma at runtime and every time you run migrations or something so we need to have a database running for it to work so i'm going to use docker for that if you have never used docker it's pretty easy to use i really recommend it to to for local development so i'm just going to copy here version of the docker compose file should be free we will have only one service which will be called db it's basically our database and will be a postgres of version 12. we will export the ports of the postgres so that means that we can do requests to that port and will be available on localhost the podcast user is postgres password123 very secure as you can see and the database is nestgs so let's go ahead and just modify our env file to suit that so the user is postgres the password is one two three and port is correct and database is nest gs right so now we need to run that that database i'm going to run it here so you guys can see docker compose app here we go the database is running we have postgres running so i'm going to split the terminal and here i'm going to run my commands and everything i can say npx prisma migrate div so i hope that it will migrate everything and we will call the initial migrations in it so basically it's just going to generate the migration of files basically saying oh here we have a model of user with all those fields right and then when we want it will push those migration to the database so the database will know that it it needs to have a table of user oh actually important thing i forgot um map it should be called users right and we need to generate the migrations again because we just change it use map here we go and that's basically the process that i use with prisma when i work with prisma if i do changes i generate migrations and then when i want i push them prisma comes also with the client database so a software that allows you to explore the database and change fields and everything i usually use table plus it's there but since you guys don't have table plus and it's only on mac i'm going to use it the prisma official similar to pg admin so npx prisma studio and it should create that thing and of course it might be a problematic because because we we didn't push the things yet so let me just do something so i'm going to push all the migrations to the to the database prisma db push right and now i'm going to separate it vertically oops actually horizontally and i'm going to use prisma studio here and i'm going to use my code here like run my node.js code here npx prisma um studio so now if we if we refresh that's weird though okay so it knows that it's called user okay that's fine so we have a user field with created email hash and hashed rt so refresh token so now we need to write the code that will actually push stuff there right so so we are done with the configuration of the database um sorry if it was a bit daunting but i think it's important to to point that because i think that a lot of people will be blocked there so we have prisma running we have our postgres session running now let's create our modules so uh when creating modules i usually like to do npx prisma sorry nest g module and we're going to create alt and that's about it right and yeah of course prisma because we need to use prisma in our application so we have two modules here prisma and oat they're automatically imported into app modules since we use the cli for prisma to be able to use prisma in our software we need to use we need to add up service actually we can add the service like that as well service prisma and i forgot to say that don't create specs okay now we have a prisma service okay the prisma service i wanted to extend the prismaclient prisma client and i wanted to implement on initors on module init on module destroy okay so um first of all i want to since we're extending a object we need to call super in the constructor so that's the first thing constructor and we need to call super so we stop complaining about that and it also says that we have implemented on module um init and destroy but it requires those functions to be there so on module init and does nothing for now and on module destroy and does nothing and now typescript is happy and let's go ahead and configure our prisma so when we create a this is basically the client so in our code you can do this prisma user create something like that to be able to do that we need to inject the configuration of the prisma at runtime like this is when you when we use the cli um and it will fetch it directly from the database url from the environment variables here but we also need to say it in the code that it needs to do the same thing so in super we are going to provide a a an object with data sources and db and we're just going to duplicate that string for now but obviously you should use the config config module and we might use it later on for now we just want to have something that works and it is saying that maybe something is maybe there's a url okay got it oh my god here we go i think that would work so basically we say that when the module starts connected to prisma connected to uh to the database and we save this connect and when we destroy the module this disconnect i guess awesome and maybe it should be added sync and and a weight here and a sink and and a weight here okay now we have configured the prisma it's a bit daunting but it's how ngs work you use the dependency you create a service for that you configure it so it works right and now prisma should be working and we can leave it alone now let's go ahead let's go into hot module and create the odd controller and alt service so npxness g controller hot and no spec and also a service cool so now we have those created we have those important into the module and we can work on the auth so first things first let's um let's think about what are the modules like what are the components of our of our app we need and we go into the controller and we will need based on that thing we will need to have a local sign-in local is because well you can sign in with google with facebook with social providers with a lot of things local just means email password like um the traditional thing so local sign in local sign up uh auto refresh and old logout so we kind of have four routes right let's go ahead and do them so the first is post local sign up right and we go sign up local and we can duplicate that for signing local we can and we also need to have a logout log out and also we need to have refresh refresh so sign in local and logout and refresh tokens right so that's the first thing we also need to inject the odd service in the controller because dependency injection private odd service or service by the way when i s when we write private that just means that just means that you probably know it but if you don't know it that is just a shorthand for that right so instead of like doing that we just say private in the parenthesis here and we need to the define the same functions in the service that's usually the pattern so here and we have that function and that one and that one and the controller should call the sign up local sign in local logout oh my god this odd service logout and this odd service refresh here we go uh most like most of it is is fine so we have all the routes defined now um let's um let's create a user right let's make the logic for creating a user so in this service we can inject the prisma pre private prisma and prisma service sorry for the messages i'm going to mute my phone so we're not bothered by that also prisma very interesting fact here uh prisma module it needs to be global so i don't have to import the module in my old service so i don't have to use imports sorry imports prisma module right i don't want to do that you know in all my thing so usually if a module needs to be imported in a lot of like other modules i do global and it comes from nest common and just to exports the same the same sorry prisma service here we go we have we have set up the the basic thing here and we can go back to odd controller and write the first logic so so for the sign up what we do is that um we need to provide some arguments right so it will be email and password usually we use details so we create dto right i'm going to use a baron export so every folder would have a index.ts file that will that would kind of export all the files inside it so it's just a handy way to have cleaner path and we're going to have auth.dto class here and export class hot dto let me quit whatsapp so the we will have an email field which will be a string and a password field it should be a string as well and in the baron export export all from dto right and now in the sign up local we can say well i want the body to be mapped to the odd dto am i oh my god am i getting still the messages i don't understand um and we pass the detail to the function and of course since the function um signature has changed we also need to update it here right and now we can create our user so const new user is equal to this prisma create a user create data and we put the data that we want to put so email um email would be dto.email and password for the password we need to hash it first so we're going to use bcrypt but before before doing that we need to think something about about the model so i just remember it because if we go back into the prisma email is a string but it should be unique so we are going to pass that unique and that's a good example of development with prisma so you see you you have found an error here email should be like unique and the way to do it is the way to fix it is that you come back you um you generate new migration so npx prisma and npx prisma migration migrate div you can if you do if you leave it like that it will automatically try to apply them but i think in most cases i like to create them first [Music] email email unique and then only i want to apply them db push i'm sorry please don't be push yes okay and now if i go back to our prisma studio i think email should be unique i don't see if it's unique um it should be unique let's let's say it's unique and we can actually check that this prisma user find uh unique and email should be there right so it considers email as unique so it's good it's fine our logic is working let's go ahead now this is a dto so we need to validate the the input first so we need to make sure that email is not empty and password is not empty and that all of those are strings basically so the way to do it is to use a class validator in sgs um so class validator i don't remember all the all the things by heart so that's why i'm like using the documentation and as a programmer you should use it quite a lot too actually yarn add those things and i will come back i'll come back into odto and say that his string is not empty and it should import it from class validator and it should also be a string is string here we go it's not empty and is a string and that should be okay i think now it won't be really applied unless we go into into here and we do app use global pipes new validation and now if we call that route if we call the odd controller and the the the email or the password is not defined well we'll get an error let's try that we come back here in our terminal and start the app yarndev everything is working that's good everything is compiling and we go to postman create a new request it will be a post request http um here we've got oauth local sign up and we won't provide anything so if we call it it says email must be a string email should not be empty etc because our pipes are are working so very good that is working we won't create the user just now because we don't have even the logic programmed so let's go ahead and program the logic but now we can be sure that well we will have an email and we will have a password and we don't really need to do like logic to verify that they're there like it's handled by the authentication sorry it's handled by the validation we need to use bcrypt to equip uh to encrypt the password so we need to install it first um yarn yarn add decrypt and we might also need to install the types of decrypt so yarn add types decrypt and the meantime we can import all from all as decrypt from bcrypt right and we can close it we are actually going to make a helper function called hash data that will take a data which will be a string and it will return the decrypt data and the thought round will be 10 so that way we can just do hash is equal this hash data and it's in a sync function so await detail the password and the user when we create it we just provide the hash like that so now we basically have created a user in the database but there's also one field that we kind of forgot and it's the hashed refresh token but that means that we need to create access tokens so let's go ahead and use the power of typescript to say that this controller should be actually returning something interesting so let's go ahead here and use the folder types and i'm going to create a baron export it takes a bit of time uh all that guys but i'm trying to really explain how it is really done like um the main problem with a lot of tutorials out there is that they just tackle some niche things and they just don't explain that thing in context but of course explaining something in context takes time so this is what we are doing here sorry if it's a bit long and a bit tedious but this is what we really do when we implement uh gwt access and refresh tokens all that needs to be done otherwise there's no point right so um we're going to create uh types of tokens just a helper uh it it's going to be a type yes export type tokens and a type if an is access token which will be a string and refresh token which will also be a string and we export that from following the baron export patterns pattern and now we can say the signup local actually should return a promise of tokens right so now we know what we need to return it's very clear we're not lost here same here and there oh sorry oh my god from types okay so we need to import the access we need to export the access and refresh token for that we need to use gwts finally you might say because since then all we have done is basically do some setup code well yeah that takes that takes time so let's go ahead and install everything that we need to generate gwts we're going to first create a folder called strategies because we are [Music] strategies we're just going to use passport for that so let's uh check the documentation of nesgs and its passport gdp so yeah gwt functionality there's a bunch of stuff we need to install here but i'll guide you through all of that so first of all we don't need password local we need we need gwt functionality we need and passport gwt okay that to begin with yarn add all that we also need that as a dev dependency yarn add and we also might need passport that was installed uh yeah earlier yarn add and i think we're all good we have everything that we need installed now we're going to create the strategies basically the strategy is something that will verify that we have the right token right so gwts are all about um [Music] token creation and token verification so once we sign in that logic will be will not be handled by passport it will be handled by json web talking library so it will generate sorry this one it would generate the access in the refresh token and this is we're going to write it in a custom way we we write it by hand but every like when the user calls refresh this is where the passport will verify that it has the right refresh token and in that case it verifies that it has the right access token so um [Music] so what are we going to do is yeah we need to create a strategy so baron export as always then we name one at strategy 80. um dot yes and the other one will be rt so for refresh talking stra the g.t.s and and and the 80 strategy let's close that and focus on that 80 uh strategy with export class 80 um gwt access token strategy the naming always is like super bad um extends passport and now i'm not really sure okay we need to extend passport strategy coming from nest password and it's going to use the passport um password this strategy here strategy and this strategy is going to be called gwt this is how we are going to reference it elsewhere that's it and what else it will have a constructor and it will call super and this is and we need to provide those fields here uh just first field is how are we going to get the token well extract the token from headers so that means that when we call something we're going to put something in authorization and say bearer and then the token right and this just means that oh extract the token from there and ignore expiration falls we can actually delete that because the default is false and the secret or key so this is the secret password with which we're going to sign the tokens so um the uh the access token will have its own secret and the refresh token will have its own secret so that's it for for now we also have a validate function that we need to implement and it will receive the payload of any and the payload will be basically the decoded javascript um like the decoded thing so it will receive that so basically when the token is received by password it will decode it into that and it will pass that object into that payload and what we want to do is just return the payload and that way since net cs is using um express under the hood it's going to do express x rec dot user is equal payload this is what it is going to do it's going to attach the uh the palette to the user object right and this happens because we the the validated function just works like that you need to accept that it works like that and we also need to do the same for rt strategy and we're going to call it rt strategy and the name is gwt refresh and here very important thing um [Music] very important thing is that we need to say that we we want to get back the the the refresh token so in for instance in gwt strategy it takes a token it kind of destroys it and it returns the payload what we want in rt strategy is to get the payload plus the token because we need to do some further stuff we need to hash it for instance right so if we want to hash it like we said here if we want to hash re the refresh token here compare the hash of refresh token we actually need to get it so if the uh password destroys it we cannot get it so we need to tell password to pass it so it will just pass the rig um and it's going to be uh of type request from express and if it looks like i'm doing magic guys don't worry i researched it before actually doing the video so i'm kind of following more or less say a pattern so don't worry about that and of course the secret should be rt secret and not at secret you can put anything there but just for demo purposes i don't want to make it too complex either normally it should be stored into config so in in in environment variables but here we're just going to hard code it and later on we can actually create a config config file but just so you understand the whole concept and we export all those classes at strategy and rt strategy and also since we're going to use them as a provider we want to make it as injectable that way i think it it might even work without but it is supposed to you're supposed to use injectable to be honest and we're going to imple import that into the old module as providers 80 strategy and rt strategy right and if it doesn't okay there's something wrong oh and that's fine that's fine it's the error comes from the fact that we have a promise uh set and we don't return we have a promise of tokens and we don't return that so there's no errors related to passport which is good um and also in rt strategy since we need to also return the um the refresh token we can do something like that a refresh token is equal rec.get authorization okay authorization and we just replace the bearer by null because we don't need to bear thing and we trim it so we get the refresh token and then we return the payload and the refresh token right okay we're done here with the rt strategy if that sounds a bit weird don't worry it's normal it's the first time i worked with that i found it weird as well but hopefully everything will make more sense as when we progress so into back into odd service we basically have programmed logic that checks if our tokens are valid but now we need to generate our tokens so for that we can use json web token library but nsgs has has a library has a module that wraps that library around the module and since we're using um ngs why not using that all right so i think it's called something like gwt module and it should be somewhere here gwt module and sghwt and we we actually installed that already so let's go back into module and import it here import gwt module for register and that's all we need usually here you'll provide something like signature and secrets expiration date but since we need to generate two tokens so access token a refresh token we don't want to do it here we are going to do it inside inside the service so let me explain here once we have created the user and everything is fine actually here is an await we need to generate tokens for that so let's go ahead and create also a helper function so our code is clear we're going to call it um [Music] sign or something get tokens or sign user let's call it git tokens and what we are going to sign is a user id which will be a number and email which will be a string and that's pretty much it this is the info that we want to put into the the json web token so this is the info that we want to put here and we can choose the info that we want to put um uh like it's up to you we will have sub we will have email you can have a role for instance if you have like like admin super users and stuff like that you can put anything um but it needs to be like uh it needs to be okay if people can access it uh publicly because you should not put passport password or any sensitive data there don't do that uh it's going to be in a sync because i want to sign the the thing asynchronously okay so what are we going to do first of all we're going to sign the the uh the dtv refresh token no the access token so uh access token would be and we need to import the the gwt service that comes from gwt module that we have registered here so private gwt service gwt service and it will have functions like sign gwt service sign async and we just provide the the object that we want to sign it will be sub which will be user id and email which will be email and that's about it and here we can provide configurations such as expires in well that's the most important for us expires in uh will be 15 minutes so the number here is expressed in seconds so it will be 60 seconds times 15 so that gives us 15 minutes right and how are we going to do that is that since it's async we can do access 80 let's call it 80 and rt await promise all and this is going to be the first thing that we're going to sign and this is going to be the second thing and of course the secret as well uh secret and we just need to check into the 80 and the secret that we have it's important that those secrets passwords kind of match and i'm just using something really dumb here but obviously it needs to be a random strings of character that you should not share otherwise your application is kind of compromised and here in a stream the other strategy to be secret is rt secret and basically this is the access token and this is the refresh token and the refresh token should have not 15 minutes but something like a week so let's say it's like this is one hour this is one day times seven here we go and we need to now return it and we're going to use the power of typescript so here we say return as the the an object of those access tokens equal iat and the refresh token is equal to 80 right and here we go our sorry rt our function is should work correctly and now once we generated the user what we do is that we get the tokens where these get tokens we need to provide the user id so a new user dot id and new user dot email and we return those tokens and now since the return type matches we should not have any error and we still have an error here signup local a function those interesting let me check that so oh yeah of course i forgot to return it here we go and now our server is compiling amazing now just before returning the tokens we said that once we the user signs in or signs up we need to save the hash of the refresh token in the database so we are also going to create a helper function for that it let's go let's call it um a sync update rthash or something and what we need is a user id and the hash pretty much right or no refresh token refresh token here we go and it's going to be a string actually let's call it rt to be to be correct and what we're going to do first is to generate the hash hash is equal to this hash data rt and it's a sync so here we go and then we do a weight um this prisma user update where id of the user is user id and the data that we update is oh my god the data that we update is hashed rt is equal to hash and that's it and so before returning it we can say this update rthash and we provide the user dot id sorry new user dot id and we provide the tokens dot refresh token so that actually does what we need to do now let's go ahead and implement a similar similar logic for actually i had this function here what is it called yeah for the other functions so um let's just organize our code a bit so those are the utility functions your utility functions i usually put them at the end and this is like not really utility function like for me utility functions are more like pure functions they they just get an input and get an output and this actually changes the database so it will be there uh at that point we can actually go ahead and generate a user to see what it does because for now we have been coding for quite some time let's go back to to the local sign up and create a user and see if we get the right answer right so out local sign up and we go to here uh for url encoded and email would be test gmail.com and password would be one two three four five very secure as you can see let's go ahead and generate it and voila we have access token and a refresh token and if we go and copy that and put that into gw dot io we we have the the right data and we can go in prisma and refresh that and see that we have actually created a new user let me make it bigger with a hash of the refresh token uh so it's just uh really cool right so let's go ahead and implement the sign in route so those two routes will be kind of public route this will not be public and this will not be public and we're going to implement that purchase pretty quickly so for the signing it's quite easy to be honest as well so we are going to provide a dto of odd dto and of course here we also need to provide the odto and it should also return the the promise of tokens to us so first of all we need to find the user by that by the email right find the user by that email and compare the hash of the password so to make sure that the password that is provided is correct so user is equal await this and this prisma user find a unique where email is equal to dto.email right and here we say um if not user if for some reason the user is not found throw new forbidden oops forbidden exception something like access denied and now we need to compare the password uh pass word matches is equal to a await decrypt verify come on decrypt verify where is verify a compare okay compare and it takes the data so the detail.password and it takes a hash so user.hash and if not password meshes then we also throw the same error here we go now if all that is correct we can assume that the password is correct and what we do is that we just copy here the logic here so we generate new tokens again and it should be user and we update the hash right so we save that and and here we need to say that it returns the promise of tokens so our our controller is happy and there's something wrong here saying function hmm interesting oh again i forgot to return oh my god and here is actually the same problem right so now we can go here into our postman and create another route with the same kind of stuff and we can sign in right and check the hash here so it's ending by oh my god it's ending by l u slash alu so we're going to sign in it should get us new tokens and the hash should should change yeah and it did change so that proves that our logic is kind of working obviously when you're developing your application your reapplication you write tests you write integration tests to test all that but for for now it will it will work uh okay so that those two routes are basically public routes and um i'm going to make a short break you can make one as well and we're going to get back to our code to implement the gwt authentication and refresh tokens on those two routes see you shortly hello hello and we are back and i think i got a new energy sauce again which means another cup of coffee because i just have been recording for for the whole hour and guys it actually takes a lot of energy to be honest and i think that you saw that my voice stone kind of lowered down as we were going but i will try to maintain it at a constant rate and try to trust me all the excitement that i have about that subject because this subject really made my life miserable for a long time and now that i got it i think that i wanna i wanna just share it okay so we have our local sign up and our local sign in working all right and yeah by the way since we're there i will delete that because it doesn't mix like it can work without and it's more beautiful i think and we now need to implement the log out functionality so what is the logout functionality um here basically we call we call the server saying i want to log out so meaning delete my rt so nobody could refresh my credentials and get new access tokens and not and i just wanna like next time i wanna do it i just wanna uh go through sign in or sign up thing whatever so this is what we're going to do uh this function is pretty easy so what is it is going to do is that it's going to take a user id user id which will be a number and later on it's going to just um [Music] well it's going to this prisma user update and here one small trick is that i'm going to use update many and not update because because because if i do update it just takes basically the unique fields and i can either say where i'm id or where email so id is equal user id but i cannot say for instance where where hashed refresh token is not null that i cannot do but i could do it in update many and the reason why i want to do it is that let's say somebody wants to spam my logout logout button or something i don't want to get i wouldn't i don't want to get have a lot of database calls setting basically that field to null because when it's not not null basically so i just will say uh hashed refresh token uh i think not no right and the data i'm going to update is hash refresh token sql node so that means that hey get all the users get the get the user by user id only if the hash talking is not null and set it to null and and that's about it and here is complaints because it expects a user id and we don't have we have don't provide it so for now we just wanna like uh comment that out also those ones uh i wanna set a custom code so http code http status will be created oops created it will just return 201 this will return okay so 200 this will return 200 and this will return 200 pretty much because usually if the default behavior of a post request and the rest standard it's that it returns 201 because supposed to create a resource here i just want to return 200 right so that would work but now the problem is that we cannot get we should get the user id and the user id where should it get it from well we should get it from the token here the subfield and for that we need to use gwt tokens with passport so let's go ahead and use those so we want to log out here and [Music] let's implement it so how it works is that you need to put the strategy in a guard so basically a guard is like a function in front of your of your of your route that will say okay can you execute or not and basically it will check for the access token and it would check for instance for the expiration date and if the signature is correct and if it is it will allow you to call log out and if it's not it will throw you a 401 um kind of exception so the way to put the guard in front of our route is to go to controller and use the the nest use guards and they have a odd guard already defined and it comes i think from where did it come from yeah odd guard is a guard that comes from passport so it is so aware that we're using password gs and here we just need to say what is the name of the of the um of the strategy and it's gwt because we have named it gwt here so had we named it differently we would have to put a different thing for instance here that would be gwt but for the refresh token that would be gwt refresh right and that will expect us to provide a gwt refresh token so that's about it so what is doing passport passport remember it takes the token it decodes it in the payload and the payload just so you know well just so you you have an idea what what it is to ability payload [Music] let's put it it will be a sub it should be a string it will be an email which will be a string so those are the two fields we have defined obviously it will have other ones so it's basically that so it will return that object to to be so you can use it in your app and the way to use it i said remember before is that it attaches that object to the reg.user so we need to have access to direct.user and let's go ahead and do it here later on we'll use a much more cleaner way of doing so but for now let's just go ahead and um and use it so rec rec and it's of type request and i think it comes from user and what we're going doing here is that we say user is equal rec dot user right and here we provide user dot id so we basically get the user object from number request that is attached by the strategy and we pass the id of it and you know what we're going to do that i think that that would work user that okay yeah we're going to use something like that obviously this is a really really it's not good so we're we're going to create a uh another way of doing so but for now let's just test it okay for now let's just test if that works so our code is executed so that means we don't have errors we'll go back in postman let's go ahead and sign in again so we receive a new set of keys we have access token and refresh token we copy the access token and um [Music] we go back to log out right and here in authorization so in headers we said authorization bearer and we paste that re access token not the refresh token and we check our prisma so once i execute that the hash should be set to null and we we have a 201 sorry 200 that means everything is working correctly and if i refresh the harsh is set to note so this is something that we want to do and i can call it different times it does nothing pretty much all right so everything is working correctly we can do the same for the refresh tokens uh let's go ahead and program that logic first because we haven't programmed that logic uh yet so for the refresh talking what should it do if we come back to our diagram here where is our refresh thing we call the refresh it compares the hash of the current refresh uh talking with the ref with the hash of the refresh token that we have stored in the db and if it fits it returns the token okay um let's think a bit how we are going to do that and and clean that a bit so rt strategy it returns the payload and it returns the refresh token so if we go back here we need to have what we need to have the user id first of all because we need yeah we need to have the user id and then we need to have rt so it will be a string and we do the same thing here we we get the request and we get the user and we say user dot id and user if what happens refresh token and that should be it so now we can get the user first user is equal it's in a sync function await this prisma user find unique where id is equal user id all right and well first of all if there's no user throw new forbidden exception access denied something like that and then we're going to compare the hash of the refresh token so uh rt matches is equal to bcrypt compare rt with the user dot hashed rt right so if rt does not matches well you cannot get a new pair of tokens so throw access the knight or more importantly um yeah let's just throw access the knight for now we don't really care and if it works what we do we just repeat the logic that we have set here um we generate the tokens we update the the hash of the user and we return those tokens so this is how refresh is working and let's go ahead and use that we are now logged out so we need to sign in again so if i try to if i try to call logout for instance um [Music] it will not do anything but if i try to do a refresh it will say unauthorized first of all because we have provided a access token instead of a refresh token and second of all because well the hash is null so let's go ahead and sign in out local sign in and we need to provide the body here so we have the access token and the refresh token and now we're going to save the refresh token and call out refresh and it's okay idiots sorry it says undefined now we need to go to headers and add the bearer token to that which will be this time a refresh token bearer and that should send us oh interesting internal error so let's see what what went wrong it says lines with things are optional find unique so something is wrong with the odd service 50 30 59 it's here something is wrong with the way we fetch the oh of course [Music] that is actually bad because it is a subfield here hmm i think we up something there guys yeah but how come how come the update menu was working for the lockout i think yeah i think it update many was not behaving the way i thought it would behave this is why tests are important guys i like i up big time because i was i was getting the id while in the talking it was sub that's why typescript is kinda important and let's let's make sure let's make sure we yeah this this happens because i was like doing it manually and uh i was not using the power of typescript and i was not using all the tools that nascs gives us so basically i assumed that it was id but in reality the the access token has a subfield here so this kind of worked because i used update many and [Music] i need to investigate that but that's bad and it's basically should be used up and not refer and not id so in that case that is working we get new access tokens and if we uh we log out that should work as well there are lookout here we go that's why also the yeah okay um so good to know we have now then uh implemented everything and now um let's see how we can improve our application first of all the guards we can create uh custom guards and use the and use that instead of like having the strings here that's the first thing we can do second that user and the the reason why we got that error from the user is because we use that kind of approach and so typescript was not able to tell us bro the id field is not does not exist so i kind of force it so now we're going to use a a uh a decorator for that let's start with the guards and then with the decorator so we will add a common folder here and we'll add decorators and guards so for the guards index.ts for the baron export and it will be at guard dot ts and rtguard dot yes so 80 is export export class hd card extends extends odd guard gwt constructor super because when we are extending something we need to call super and same thing for the rtx except that will be rtcard right and at guard is pretty much simpler simple and rt card as well we export those at guard and rtguard right so now in our controller instead of having that we can just say at guard and here will be rtcard right um that's it next thing the decorators it's what we're doing here is quite bad so let's go ahead and change that decorators it will be index.ts and get current user the decorator that yes and for the decorator it will be a param decorator so export cons and get current user create param the creator the param decorator is taking a context execution context and what it does is that what it does is that it is supposed to um get the user so context get http context switch to http get request and what we are saying is that the we return request.user right and we also can provide data which will be a string or or undefined so if not data we just return request dot user and else we return request data sorry request user data so that way if we just want to get the subfield we can just say something like let me just check if the decorator is exported and here we go we need to get oof what happens here what happens get current user and we need to do is writing user id number and give me the subfield all right and in that case it's going to be a number we know pretty much yeah user id there are better ways of doing so uh for instance you can have get current um user id the creator and instead of um like having a get user a current user we can just get the um the user id so we can even do that if we want that will i think further uh make sure that we don't have get current user id problems because here we know that it's a sub um and yep so we can export it here and import it there so here we are sure that it's a it actually get current user id user id and it says that it returns it returns that i'm wondering if it's possible to tell it that it's number i'm wondering yeah i'm not sure if it's if it understood actually but yeah basically we know that it's a user id even though behind the hood it's a sub so there will not be there will not be any confusion and we can actually do the same thing here right so except that here it's not get current user id it's current user refresh token actually refresh token yes so it will be a refresh token it will be a string and refresh token and we also can get the the id get current user id which will be user id to user id number okay and user id so hopefully it's all tested out now um the thing is this will accept the the gwt guard but this will accept the rt guard right so it's everything is pretty much clear but you know our application will grow so we will have more modules than just the old module so that means and in our application a lot of like endpoints are protected by gwts like most of the endpoint in your app will be protected by the at guard but it's not very efficient to actually go ahead and set it at the every handle imagine you have like hundreds of handle and handles is that that's a that's a handle that's a handle and that's a class you can there are several solutions you can either uh set it uh globally or set it by class or set it by handle usually i i set that globally right and on the routes that i want to keep public so the routes that don't don't need uh the gdwt token i i say something like public right that requires the use of metadata but i think that makes the application much more readable so let's go ahead and implement that so first of all we're going to get rid of the used cards here we are going to implement it we're going to implement it at the global level right so we are going to go ahead in main.ts and say app use global guards and here we say what's the name again it's at guard right so all good here but now the at guard will basically require um the the access token for everything so even though this is supposed to be public if we want to like sign in local sign in and we just disable that and send that it will say unauthorized because it wants the the gwt so to be able to say i don't use the at guard for that route we need to create what's called what's called we need to use metadatas basically so uh the way to do it is that we are going to create a decorator and we say something like uh is public like public dot decorator the ts and here is going to be a export class export class take or public is equal to set metadata and the function provided by um by sgs and it's just a key value that is accessible by other codes if you want so it is public and it's going to be true wait is public is true yeah and what's happening here or sorry expert const public um so it's basically a function that returns um the call to set me the metadata is public to true and we need to export that from public and we're almost done guys we're almost done so here we are going to say public so those routes should not be used in the at guard and now we need to go to the 80 guard and tell it well everything every time you see something with public please don't don't check for access token right so we go to 80 guard and the way to check if a handler so this is a handle or a class has some metadata is to use the reflector so it's let's call it let's do it here so we're going to inject private uh reflector reflector and it comes from nashes uh sadly because we are using it in a global like here in the global bootstrap script it does not support dependency injection like there are several ways uh several solutions to that we can either either use it here but then we need to create a reflector sql new reflector and pass it there to simulate the the um the dependency injection or we can use it in app module so here that will be providers and it's going to be provide up card and we say use class and who's going to be id card so there are two ways of doing those things either in the app module or like that in the app module it will automatically detect the um it will automatically inject the reflector but if we're doing in global we need to do it by ourselves so it's two ways in that case i'm going to use it in app module i don't really have a preference from for either of those like if it's something simple you can leave it in the app uh bootstrap but if it if your guard requires a lot of dependency injections then it might be actually good to like if the guard requires a uh prisma or like stuff like that it's better to put it here so this solution is more stable scalable but a bit more ugly because you see that that is uglier than that but i'm going to leave it here so you guys know how to do it either ways and now we go back to the id guard because we need to say to the 80 guard if you did like if you detect something so for instance uh since it's a guard it implements can activate and basically they can activate a function it says if it's true then it will not ex activate so it will not check for what is it is supposed to check so if i set it to true it will let me sign in and it will bypass it if you want if i set it to false it will block everything pretty much even like it will block everything even if i provide a good uh wt it will block it because i said whatever happens it's false don't let it go so we just need to create some conditions here we need to say if if we detect a is public metadata you see is public set to true then we bypass it otherwise we don't bypass it and we execute the the can activate of the odd card right so it will be something like return super can active super that can activate here oh yeah and yeah we need to pass the context context the context should be there context execution context and it's very similar to what we had in the in the param decorator so basically here we need to use a reflector so public is public is equal to this reflector and get all and override this is what we're going to do we are looking for which metadata we are looking for is public metadata and where are we going to look for it well by priorities first of all the handler context get handler and then context get class so that means so try to get the is public metadata first of all check it on the handler and if you don't find anything shake it on the class this is a class and if you don't find anything at all it will be false and if you find something that is true just return true so basically if is public return true so if the endpoint is public bypass it bypass the guard otherwise execute the card based on the the guard that we're extending and we are extending odd card an odd card will automatically detect the strategy and it will check if the token is correct and everything so here is our guard and here is it done so now if we sign in it should work something is not working okay let's check what is not working uh can't read properties of undefined reading get all an override interesting why isn't not working and i think the reason why it's not working is that it is not declared as injectable so let's go ahead and try that and it is working so don't forget to put injectable when it's um it requires injection dependency injection so our guard this one does not really require a dependency injection this one does so injectable right and i think there's another one public decorator get current user yeah it does not really require dependency injection so that's um that's quite uh interesting so now it's working we can sign in we can log out so log out authorization bearer it it locks you out so logged out and if you try to refresh or very important thing about refresh all right so let's go ahead local sign in um local sign in so we get the refresh token let's say we want to refresh now what do you think will happen better refresh token okay so if i do like that it will say 401 why because if we go back to let's close everything and open not odd.net controller if we go here and we check what is happening here um this route says i want to use odd card um a refresh token guard and that's fine but before that guard is executed there's another guard that is executed globally and this card is access talking guard so in order to avoid to execute that guard we need to put public here as well so what is happening is that we say bypass the odd access token guard then use the other guard that we have defined the rt guard and in that case if we do it like that we should be able to refresh the tokens and it's not working either so let's see what is going on here um [Music] yeah everything is working correctly hit this it is working well it's written 401 interesting let's see let's see what's uh what's going on uh how we have we have we have we okay have we assigned the thing correctly so sign in local where is that at secret irt secret the signature seems to be correct so what is there here in the strategy 80 strategy 80 secret uh or rt secrets so the signatures seem to be correct okay what is happening with the cards we have defined in the common thing so at guard is gwt and rtgot guard oh here this is error gwt refresh it should be refresh here and now if we do it again [Music] now we have a 500 error well it's good that means some progress data and hash arguments required so hot secret data and hash arguments are required so we're not passing the we're not passing something refresh token so user id and rt and if we call that route we get let's console those probably something is happening with those things and it is not working like i was expecting it let's uh call that again and see what is happening there user id but refresh token is undefined okay let's see what is going on here it is supposed this strategy is supposed to put the refresh token here in the user and get current user is it working correct oh oh yeah i'm stupid like this this one's a stupid move of course it's not data it's da as a variable this was a very stupid move i hope you guys have seen it before me so now if we do it again that should work finally finally so let's recap we are logging out with access token right we are logged out we wanna refresh it won't work not arise so we need to sign in with the body and now we get the access pair like the access and the refresh token pair and now if we want to refresh go to headers and say bearer a new set of tokens is created so it's finished by gq as soon as we refresh the hash changes so so so lo so basically if we do log out if we want to user if we want the user to log out he needs to call the that endpoint and he will not be able to refresh the refresh token and access tokens anymore right if we notice that um a user is compromised what we do well we just go into database client and we say his refresh token no so if he tries to refresh again sorry no and actually it should be it should be should not be a 500 and what is happening here again it's incredible everything is there id is okay refresh token is okay and it says data and hash argument required okay something is wrong let's recompile everything so if we are going to refresh it we get the right values but it is saying that data and harsh argument required something is wrong with our logic then user id rt oh yeah oh yeah okay i see it's because we're trying to compare the hash but since we locked the user out well it's um it's it's locked out so we just need to create a new condition if not user or not user hashed rt we throw that exception and now if we do it again now we see that access is denied and i think i think that's it folks i'm just going to clean here verify that everything is correct and to my to to what i have found i think that this is like one of the best ways to handle authentications with uh with the gwt if you want to do it alone like if you want to create created so you have those guards and the most important thing i think in this in this video is that concept of log out and refresh and how it's supposed to work together so i'm going to upload the source code of the of the project on github feel free to download it play around with it if you have any suggestions if you have any any um any other questions i'm more than welcome to to to examine those and maybe make some other videos so it took so long but this subject is quite complex and as you have seen programming it like that without doing any testing and stuff like that testing it manually it can um yield a lot of errors and it can create like a lot of problems so i hope it was useful for you guys leave a like subscribe if you haven't and see you in the next video
Info
Channel: Code With Vlad
Views: 1,175
Rating: undefined out of 5
Keywords: code with vlad, nestjs jwt authentication, nestjs jwt, nestjs refresh token, nestjs jwt auth, nestjs jwt guard, nestjs jwt token, nestjs jwt secret key, nestjs passport, nestjs passport jwt
Id: uAKzFhE3rxU
Channel Id: undefined
Length: 104min 13sec (6253 seconds)
Published: Sun Dec 05 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.