NestJs - Guards & Authentication & Authorization [08]

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello guys and welcome to episode 8 where we talk about guards and authentication to better understand the role of guards let's take this example let's say you have an API that returns posts and the route is slash posts with the method get and let's say there's a client trying to make a request of course to fetch your own posts on a social media platform you have to be logged in not everyone should be able to call that route so it shouldn't be public instead we have to set and place a guard that makes sure that the request is actually valid so it might check that the user is actually logged in has a valid JWT token or that the user has the required permission to access a certain route so this is where the logic of validating a request and permissions should be and this is what they say in the documentation cards have a single responsibility they determine whether a given request will be handled by the route Handler or not depending on certain conditions like permissions roles and so on now validating permissions or roles is known as authorization but of course you also know Authentication let's store the confusion between the two authentication refers to who you are your identity so whenever a user logs in into a system and the credentials are correct the system now knows who that user is the user is considered to be authenticated and most of the time the server is going to provide an access token a JWT token to that user that they can use on every single request that they make that way the server checking the token and verifying it can actually make sure that this user is who they claim to be this process is known to be authentication it refers to the Identity or SV process of verifying someone's identity on the other hand authorization means or refers to what you can actually do on that system so different authenticated users can have different permissions or roles authorizing a user means that you are verifying that this user who is already authenticated has enough permissions or possess a role that is required to access a certain resource on that server or perform a certain action authentication and authorization play hand in hand they are both handled by the guards now you can have one single God that does both authenticates the user first and then makes sure that the user is authorized to access a certain route or you can also have two different separate guards one which is the authentication guard which just validates the user the identity of the user and then if everything is fine it's then going to call or the request is going to go to the second God which is the authorization code which is going to make sure that this authenticated user actually possess the required permissions that are required by a certain route before accessing it if everything is fine then the user is going to have access to that route or to that resource and perform a certain action if not then it's going to throw another and we're gonna see how this is done so make sure to watch the video Until the End now if you've used Express before then you know you used to use middle words to authenticate and authorize requests but middlewares are done by Nature they don't know which Handler is going to be executed after calling the next function on the other hand guards have access to the execution context instance meaning they know what's going to be executed next which Handler is going to be executed next so it's a good idea to actually use guards and not middle words and nests since it has that extra Advantage so let's say we have this route Handler and we had some metadata saying that the roles should be admin if you want to access this create cat route Handler if you're using a guard like we said you have the context and you know which route Handler is going to be executed next and you also have access to the metadata of this route Handler which is very important if you want to check the permissions or the roles so this is one more point or this is to show you why you should use guards so this is just to show you why you should use guards instead of middlewares whenever you're working on authentication and authorization it's also important to know that guards are executed after all middle words we've talked about middlewares and how to create them in episode 5 but before any Interceptor and pipe and we've seen what pipes are in the last episode and how to use them to validate requests validate data or the shape of a request now that we understand the role of guards let's see how we can actually Implement our first chord first of all I went ahead and created a folder called guards and then I created a file authentication.guard.ts let's take a look the first thing that you can see is that a guard is basically a class just like pipes and so on and it also has the add injectable decorator the one we've seen when we created our own pipe and we've seen one we created our services meaning they can be injected and can be handled by the ioc container of nest.js we can also see that it implements can activate which is an interface and this interface provides a method that we should Implement which is called can activate which has access to the context like we've said this is the advantage of using guard and which returns a Boolean true false or a promise if we have async or an observable like you know in rxjs so of course what we need to do next is to actually Implement that method let's go ahead and remove this line and just keep return true and let's add the log inside the guard okay and now let's go to our customer controller and let's bind it to a single route Handler we can bind it on top of a controller just like we did with the pipe or on top of a route Handler of of course just like pipes again but now let's try and do it on the get or custom route Handler just like we have at use pipes you can also have ADD use guards which is also from nest.js slash common and it can also get a an array of cards as you can see now let's go ahead and call our authentication guard so now it's bound to this route Handler of course we could have said new here but we're gonna leave the injection to the ioc container okay now let's go ahead and call this route let's open Postman and then hit on send and as you can see we got inside the guard and then here we got the array coming from the route Handler because here we are returning this and the array and this is because we have returned true we got access to the route Handler if we had return false and we saved now if we hit on send as you can see we actually got an error status code 403 Forbidden resource and forbidden now the shape of the adder might be different for you if you haven't been following or keeping up with the series but basically I have an HTTP exception filter here which is custom where we shaped our own errors but either way you're going to get forbidden resource and this is the default Behavior buying sjs if you throw false in a God it's going to throw forbidden now usually whenever you have authorization this is a good error to show but if you have authentication only and you're trying to authenticate a user it's better to show instead of forbidden 403 unauthorized 401 for example and to do that instead of returning false you can for example say Throw new unauthorized exception and then now if we save and open Postman and hit send as you can see we got unauthorized 401. now this doesn't matter if you don't really care about the other throne but as you can see if you have Force it's going to throw another if we have true it's going to enter the route Handler now if we try to call the create API so the second one post hit send as you can see it was created and then if we take a look here and the route Handler logic with body which is this one here and it did not actually print inside the guard and that's again because the guard was only set on that specific route Handler now if you go ahead and instead remove it from here and add it on the controller level now if we hit on Save and try to add another one if we had send again as you can see first of all we got inside the guard so we actually entered the guard done our validation validated the user made sure that everything is alright and then if that's the case we're going to go to the route Handler and then process that request now I added the code that was present const request which is retrieving the request from the context now again if we take a look at execution context we can see that it's an interface that extends argument host which we've seen in a previous video which I'm going to talk about again we can see that we have a get class and then we have a get handle function which returns here Handler method or a reference to the Handler method that will be invoked next in the request pipeline which was the advantage we talked about now if you go back to argument host and we see here we can get the arguments return the array array of arguments being passed to the Handler we have switched to RPC to http or to WS here which is in the context of websockets here is the context of HTTP which is our case and we've seen the use of this in our HTTP exception filter before where we had an argument which was called host which is of type argument host which was used to actually get the context but we need to switch to http since this is dynamic and we could have heard switch to what for example and then from this we used to get the request and then we would have the arguments of that request now here it's very similar but instead we have execution context which inherits from argument host so it has the same method and methods and arguments but on top of that it also has get Handler the reference of the Handler and then get class which has the reference to a controller so now it makes sense here looking at this context will switch to http.getrequest we're actually getting the request we can also say console.log for example request dot let's say request.headers to print headers let's open Postman and head on get sand and as you can see here in the logs we can see that we actually got the headers of that request that we sent accept cache control host localhost 3000 connection so now usually whenever we have authentication or authorization we care about the authorization part of the headers so let's let's come here and then add some authorization let's say better token for example and add anything and then is going to be added to the headers of course if we take a look here authorization and now if you hit on send if you take a look here we can see that we have authentication better and then here we should have a token which we should validate before returning true or false so now you can see that the guard is actually starting to make sense now usually whenever we log in if the credentials are correct we receive a token but I'm not gonna go ahead and create an authentication module now and add all of that functionality so instead we're going to be using this website jw.io to actually simulate or to actually create our own token and then validate it and see how or what happens in our guard since this video actually talks about guards first of all you should have a secret key of course this should not be in a configuration file and of course not in this file as well so not in a config file nor here it should be in a environment variable file so in dot EnV because this shouldn't be committed to get be careful this is very important you should never comment any secret to get but for the purpose of this video I'm just gonna keep it here just to be quick usually you would have that in an environment variable and then in the config file you do process.env and then you read that property and you never committed you added to get ignore the EnV file should be and get ignored anyway now that you know that to create a token with this secret we can go to jwt.io and then here I'm going to place this token and here let's say name John okay so now this token has some information the payload which is the name of the user that's logged in which is John and of course we signed that with our own secret now if you take this and then in Postman add it here and hit on send if you go back to our code as you can see now in authorization we actually have a token that is actually a JWT token so of course we can use a library to decode that token and to verify that it's valid to do that you can go to the documentation and authentication and security authentication you can see that you can they actually have a JWT module that they can register and you can actually get it from nest.js slash JWT so we can install that package and then we can specify the secret here the option of signing a token so whenever you're actually creating a token you can set the global configuration here when it's going to expire and of course other properties and you can do it whenever you're signing the token and not actually Place those information here either way let's go ahead and see how we can Implement that so inside of our customer module inside of imports I'm going to add this module of course now we cannot actually have it here we cannot import it because we need to install that package so let's go ahead and do that so npm install JWT now that it's installed you can actually import that package and then that module and then of course here in the secret I'm going to add the secret but again this should be in an environment variable file and then using a config file you can read it and then in the config file you have process.env so you need to keep this as a secret and then we set this as Global true so anytime we use the JWT module anywhere in our application in any auth code in any module anywhere is going to use this secret and then those sign in options if you have different secrets you don't have to set this as a global you can have different modules with different Secrets or set the secret whenever you're actually signing a new token if you need to change the secret but for now let's keep it simple to use the JWT module we need to have a Constructor here then we need to inject so private JWT servers of type JWT service I had to do the import manually but now if you take a look at this this.jwt service dot we can see that we can decode a token we can sign it and here whenever we sign it if you take a look at the properties we can add our own payload now we're doing it manually here our payload is named John Doe but of course we should do it in a code and so whenever the credentials are correct you can create and sign a new token and then you can also pass another argument which is an options argument so here let's say the payload is something and then the options if you take a look we have so many things here header issuer we have the secret this is what I meant earlier when I said you can set the secret whenever you're signing a new token so if we don't set a secret here for example it's going to sign using the secret here the one registered here globally but if you set it manually we can actually override that secret but now we don't really care about DOT sign we care about verify and verify what it does is it actually decodes and makes sure that it's correct because decoding a token only gets us the payload and some extra information it doesn't verify that this token is actually valid it doesn't make sure that this token actually was created by our server using this secret so of course we would need to use verify I made our authentication guard a bit more useful now we are actually getting the request like we've seen and then we're getting the token from the request by accessing headers and then authorization and then splitting on the space and taking the first index so not index 0 index 1. which is here headers request.theaders.authorization and then we are splitting this which is better space and then the token just so that we can take the token part after that we're checking if we don't have a token then we are going to throw an authorized exception if not we're going to have a try catch and which we're actually going to try and verify using JWT servers this token so we need we need to verify it again we're not specifying the secret here or any other options because we're assuming that all the options are set here globally but if we didn't have the secret here of course we would need to set it here in this function so here we would have Secret now whenever we verify the token let's see what happens first of all let's see what happens if we don't have a token here so I'm gonna know us add no auth and then hit on send if you take a look here cannot read property of undefined reading split and that's because we're actually trying or we don't even have an authorization head one way to fix that is we could actually take this part and also add it in the try catch block and then now if we save and then open Postman and hit on send you can see we got unauthorized now let's add our authorization again so we have our token and let's try to hit on send as you can see we got a response from the server and we actually got access to the route Handler this time and that's because the token is valid now if you recall we are using this old secret here let's say we change that secret and we try to take this we have the same payload and now we add the new token here if we hit on send as you can see we also got unauthorized if you take a look at the error here it says and here in the catch of course we're logging it ourselves it says invalid signature and that means that the signature used to sign the token which is here this one here is different than the one used to actually verify the token which is here they should be the same to make sure that this token comes from this server and it is correct so you can see that now our guard is actually doing its job it's checking that we have a token it's checking that it's verified and correct and then if that's the case it's actually going to add the payload to request.user so now if we go ahead and take a look at console.log request.user let's save and then here of course we need to go back to the previous token which was correct hit on send if we take a look here as you can see we got the payload name John Doe and you're used to actually doing rack.user or rack dot whatever in Express whenever in our middleware we verify the user this is the same thing here and now if you you want to actually have access to rack.user inside of the controller for any reason you can say at Rack and then here you can say let's say and then you can also specify that you want the user part and then here you can say user for example and of course this need to be from imports from last year as common also what I meant to say here was not this but instead I'm going to say this so we're just extracting the user let's add the log user if you hit on send as you can see we actually got the user from the request because now we're just extracting the user part which is here set rack.user which is this the payload of the token so now instead of our controller we can have access to the payload to the user part of the request now the part that we skipped which is if you remember I said you are simulating the creation of a token here on this website jw.io at this part here the sign in functionality in your application where you have the user actually log in send their username and password and then you verify if your database contains those information if that's the case you create your payload and then using the JWT service that we're using now to verify the token you'd also use it in the first place to actually sign or create a token we already talked about the sign functionality and then you add the payload of course here we didn't specify the secret again because already we did that in N here we set the secret so far we've seen the role of a guard and where it's in our request response flow we understood the difference and the relationship between authentication and authorization we've seen how we can create a guard ourselves at injectable it's a class that implements can activate it can return either Boolean promise Boolean or observable and we've talked about the execution context and its advantage over using a middleware for validation of a user and we've seen how we can also register the JWT module so that we can actually use the JWT Service as well and inject it so that we can verify a token and so that we can also sign a token like they have done here and we've also seen how we can buy bind a guard on a controller level so it will be applied to every single route Handler and that controller or how we can also bind it on one route Handler on a specific route Handler instead of the whole controller there's also one more thing we haven't talked about but it's simple just like Global filter filters you can also have app.use Global gods and then here we can say new authentication guard or any God we want and of course we would need to pass an instance of JWT service in case we have a Constructor and we are passing it here I want to show you one more thing in this video which is using authorization guard with this authentication code and then having our own custom decorator which is the role where we specify which role can access which controller or which route Handler so I went ahead and created another file which is another guard called authorization code this time which also have of course the same characteristics of a guard at injectable implements can activate and then have the I can activate here but this time I'm just logging that we are inside the authorization guard and then I'm just returning Force so now we have an authentication guide that checks the token and verifies it and then attaches uh the payload to a user and then we have an authorization guide that just returns Force if you remember I said previously that you can actually pass an array of guards to address guards so here you can say authentication code and then authorization code if you hit save now and open Postman and try to call an API we got for better resource if we take a look at the logs we actually got the token and the actual payload which are the logs here console.log request.user and request.user which are before return true and then after returning through of course here use guards is going to continue or the request is going to jump to the second guard in our array which is in this case authorization code if you go there we can see we got this log which is inside authorization guard so we know that we are inside of this but we are returning false if this was true for example if you have we hit on Save if you take a look here and hit send this time you got an empty array so we got inside of our route Handler now the role of this guard here is going to be checking the roles of the user and comparing it to the roles of a certain route so here we're going to set some metadata specifying the role that is needed for accessing this route and then here we're going to check the user rec.user the role of that user and if it matches then we're going to say return true else we're going to throw another now it's good to know that this is a simple example in real life scenarios you might have more complex scenarios where you might have a database or a collection called permissions or a table in your database for permissions where you have a list of permissions attached to a role and then you might have a user with a role ID that specifies the role that this user have and then you might have all kind of relationships in your database so here in your authorization guard you might actually implement or depend on a module called authentication module and which maybe you pass in direct.user.id of that user and then in your database or and your module here in the service authentication module service you might have a function that takes the rack.user.id and then goes to your database fetches the list of roles and permissions and then Compares those permissions to the permissions or the metadata here that are required for this route but to make it simple in this video we're just going to add the role here in the payload so whenever we are signing or creating a token for a certain user after authenticating that user and finding that the credentials are correct we are going to add the payload so we're going to add the name of the user who logged in and then the role let's take this and then this is our new token now if you hit on send if you take a look at the logs you can see that our payload now contains a rule that we can use to compare the role of that user or that token with some role that you add here we're gonna see how to do that in the documentation we can see here that we can create our own decorators for example here the specified public as a decorator they set the name here and then they use set metadata from nasty as common and then they specified here is public which has a value of true and then you will be able to use it here so whenever you have ADD public in or on top of a route Handler you know that this is a public API for example I went ahead and did the same thing but for my own decorator which is roles so now we created a role decorator that takes an array of strings that specify the different roles we could have admin in our system maybe assistant and so on and then we use set metadata from last JS common we specify that we are setting roles to the list of roles the array of string that we pass here so now to be able to use that decorator we could go to our controller and then if we have the auth card on the controller level we could set it here at rolls and then it takes an array of strings so we could have admin this would mean that this controller can only be accessible by admins and of course we're going in our authorization code to get this metadata the required role and then compare it to direct.user role so the role that the user has also of course this works as well on Route handle level so we could have it here meaning this is only these are only applied to this Handler here and not to the other one for example well let's add it back here now to get access to the metadata of the route Handler or the controller meaning the class we need to use something called reflector which is from nest.jscore so we're doing dependency injection here and then if we take a look here you can see if we say test Dot reflector dot we can have get to retrieve metadata for a specified key for a specified Target and the target here means for example a class or a Handler you could also say get all and override let's see the difference first let's say get and then here we need to specify the key which is rolls key that we specified here which is roles and then next we need to specify the target we need to use context Dot and here we saw this before we need get class sense or decorator or our metadata here our metadata at roles is on the controller level so get class and now we can say or const required roles equal this so those are the required control rules let's make sure that it's working so console.log let's see if we actually get admin let's hit on send let's take a look here and indeed it said the required roles are and then we got admin If instead of get class result get Handler if you save and hit send on Postman now we got undefined and that's because the Handler meaning the route Handler doesn't have anything like it's this is not on a route Handler it's on a controller now if instead we had this on a route Handler get our customer if we save and now hit on send if we take a look we actually got the required roles or admin and now if you had class instead of Handler and now it's not on top of our class it's still on top of a route Handler a fit on send again we got the required rules are undefined now in your code sometimes you might have the guards and the decorators set on the controller level and sometimes they might be set on the route Handler level so it's a good way or it's a good practice to make it Dynamic meaning instead of using that we could use get all and override this says that S3 is metadata for a specified key for a specified set of targets and returns a first not undefined value so here now instead of specifying one target we have an array of targets we can say context.getclass and contacts.get Handler now if we save and of course this should be like that if we save and hit send we got the required roles on admin that's working for the controller level and now if we set it on a route handle level if we save and then hit send we also got the required roles our admin so now using the reflector you are able to actually get the required roles or the metadata of a class or mini controller or a Handler now to get the user role we can say const user role equal request dot user dot role which now if you remember here we are setting the request.user and then here we already set the role as admin now for the sake of Simplicity instead of having roles I'm going to just call this as roll then roll here and we're gonna pass in one specific role so it's no longer an array of strings it's now one string only so of course here it needs to be let's pick this role and then instead of an array of roles we're gonna have one rule which is admin so let's go ahead and log the user role as well if we hit on send on Postman we can see the required roles are admin which is now required role and then we have the user role which is also admin I removed all the unnecessary logs and now what we can do is simply say if required role not equal equal to user role then we can return false if not we're gonna return true if we hit on Save and now open Postman and hit on send we got the array that's perfect and that's because both are admin but then let's say instead of being an admin this user was actually an assistant so now if we take this new token and then set it here this is an assistant token if you hit on send we got an error forbidden resource and that's because the required role is different than the user rule again in real life scenarios you might have a list of permissions instead of actual roles but this the idea is the same you have a reflector you have some metadata you fetch the metadata from the controller or from the route handlers you have your own decorator you specify what values those decorators can take and so on it could be simple it could be more complex but the idea is the same if you have any questions please make sure to leave them down in the comments if you're still watching please leave a like subscribe share this video show your support thank you so much and I'll see you in the next episode
Info
Channel: Computerix
Views: 6,320
Rating: undefined out of 5
Keywords:
Id: w_ASqSZKhMQ
Channel Id: undefined
Length: 33min 4sec (1984 seconds)
Published: Sun Jul 23 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.