GraphQL Authentication: JWT, login, signup, and more! | NestJS PassportJS Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey how's it going today we're going to talk about graphql authentication in sjs specifically we're going to cover how do you do user signups how do you do logins and then finally how do you actually protect your specific queries with a jwt access token this is a fairly complex topic that i think is not very well documented so if you follow through this video i think you're going to walk away with some very solid fundamentals all right we're going to start by creating a new application using the nesjs cli if you don't already have that make sure you install dash g at nsjs cli once you have that installed you can then use that cli to create a new application so i'm just going to create one called nest graphql auth it'll ask you what package manager you want to use i use npm once that completes i'm going to open this in visual studio code all right once that's open we're actually going to go back into terminal and install a couple of dependencies so that we can set up graphql for the first time you're going to need to install an sjs graphql graphql at version 15 and apollo server express the version 15 here is pretty important because i think the the sjs graphql package here does not yet support graphql 16 so make sure you're using version 15 here all right so hit enter there all right once that's done we can start configuring our application to use graphql so we're going to open up the app module here notice that it starts out with a controller and a service we don't actually need those and you can even just delete those files if you want so what we're going to do here is we're going to import graphql module and we're going to configure it using the for root method here passing an object there and we're going to give it these two properties we're gonna do auto schema file and sort schema true what this effectively means is that we're gonna do code first graphql which means that we're writing the typescript and it's gonna generate for us the graphql schema alright so now we're ready to do some actual graphql work again back in the terminal we're going to generate some stuff we're going to do nest generate resource users this allows you to bootstrap a lot of boilerplate for you very quickly and once you enter that as you can see it'll ask you what transport layer are you trying to use in our case we're using graphql code first so we'll hit enter there and it's saying do you want to generate crowd entry points now i'm going to hit yes here because there are some stuff that i want from that generated boilerplate and then we'll delete the stuff we don't need all right so you'll see that it created a bunch of stuff in a new users folder and wired up our app module so you'll notice that our app module now automatically has the user's module in there all right taking a look at some of the user stuff that got generated you'll notice that there's a new users resolver here and that has our crud entry points right so it has like the create read update delete now for this tutorial we're not actually trying to do crud we're just trying to do authentication right so we're going to delete the remove and update user and we're also going to delete the create user here because we're going to do this later in a different module in our auth module i don't want that to be in the user resolver all right and we can clean up our imports here a little bit similarly in the user servers we can delete the update and remove in here we'll leave the create in here because we are going to utilize this later you'll also notice that we have a new user entity file and this is going to represent basically the shape of our user and notice they have an example field in here which we're not going to use so let's just rename this to id clean this up a little bit we'll also add a new field for username since we know we're going to be doing logins later and in general we're going to be saving that username in our mock database speaking of mock database in our user service this is where typically you would create queries in here to actually talk to your database and you can do that with things like prisma or type orm i would recommend either of those both of those are very good and there's videos on that in my channel if you want to use those specifically i want this video to be focused on auth so we're not going to talk too much about the database so what we're going to do here is we're basically going to create a mock users database that we can read from and add to as we log in and sign up users so i'm just going to create a private users array here and remember with our entity we have an id so let's do a one here we have a username and technically we also have a password right because we're gonna do login so they have to have some kind of password now i'm explicitly calling this out as not secure because it's not right we're not supposed to be storing passwords as plain strings we're going to improve upon this later in this video so make sure to stick around all right so what i'm going to do here is i'm just going to create another dummy user and let's change the id here to 2 let's give this a username of maria right so let's start to implement some of these service methods so to find all we're really just going to return this.users in our find one here we're actually going to change this to lookup using the username instead of the id so we're going to do username as the parameter here which should be a string and then we're just going to do a basic array find right if the username matches the passed in username then return that user otherwise it's going to return undefined now our create method here you'll notice that it's using a create user input which is one of the things that got generated by our nest generator we need to update this great user input to have the fields that we want to be saved in our database right so we're also going to clean this up to have an id similar to our entity right and in fact you can just copy the id and username from our user.entity and you can paste that in here if you want but we have an extra field remember we have a password as well that we're going to be saving to the database now you might be wondering why the entity doesn't have the password field in it and that's because i don't want that property to be exposed in our api right we never want to return the password in any way and this will probably make a little bit more sense in a bit here but let's go back to our create here so in our case if our database is just an internal in memory array the way we create users is to simply add to it so for example if we had a user object that looks like this taking in the create user input and typically the id is generated by the database most of the time right if you have like auto incrementing ids or maybe you have guides so we're just gonna do a simulation of auto-incrementing integer ids for a database so we're going to do this.users.length plus one right so if there's already two in there that means the next one will be three finally we're going to add to our users by doing a push and then we're just going to return that created user again let me just clarify this is a mock database in a real application you obviously should use a real database some people get pissed at me for not doing things the real way you know my job here is to hopefully teach you the fundamentals i'm not giving you a production ready solution that is for you to figure out now like i said if you need help with that database stuff i do have videos on both prisma and type rm in my channel so make sure to check that out all right with that in place let's actually just run our application at this point and see what what what does it look like how does it behave so we're just to do npm run start dev and i actually forgot to change something here so in our resolver our default resolver that got generated this is still passing in a number id so we need to update the args in here to be no longer an id it'll be a username as we said and it'll be a string all right with that updated you'll notice the terminal is all green now now once your application is running you have to go to localhost 3000 slash graphql and that's where our api is actually mounted so within here we should be able to start doing some basic queries so for example i think if we just do users and let's bring back the username and id hit the play button here and we get back our two mock users that just make sure that our application is running you'll notice in the file structure that we now have a schema.gql here that got generated and this is going to be a mirror of the code that we wrote that represent this that represents this schema right so we have a user with an id and a username and we have a query for user and users and this is automatically going to be updated for us as we manipulate the code all right so let's talk about where do we go from here so we said that we're trying to do authentication right and we haven't done any authentication yet now what i want to do is we're going to implement user sign up and logins and then what we want to do is we want to protect this this query right we don't want just anybody able to query all of our users we want them to be logged in right so we're going to do a to-do here to protect this you know with a jwt which we'll do in just a second well in a couple minutes all right so now in the terminal we need to actually start setting up our authentication layer and we're going to do that through passport which nastjs has really good support for so we're going to install a couple of things in here so we're going to install an sas passport passport itself and passport local passport local is a specific strategy for passport that's going to allow us to log in using a username and password now in our case here we're doing username and password logins you obviously could do other ways of logging in right like you can do like google logins you can do facebook logins twitter logins whatever you just have to swap out the different strategies with passport to allow that to work in our case we're just gonna stick with something simple with our own username and passwords once that installation completes we also need to install the types for passport local once you have those installed we'll be back in the terminal again and we're going to generate the new module for auth this module is going to contain all of our authentication concerns you know login sign up and stuff like that we're also going to create a new service for auth we're going to create an auth service and then finally we're going to do a resolver for auth all right so if we go back in our code we now should have a auth directory here that has the auth module it has an auth resolver that's empty and it's going to have an auth service now our auth service in a little bit is going to be used specifically for validating if a user is in our database and if it's if their login credentials are valid so we need to create a constructor here that injects user service so we can actually use the methods that we created earlier like find one so what we're actually going to add in this auth service is a validate user method which as you can see is taking in a username and a password because we're doing that type of login and then in here we need to provide the logic to actually look up the user and make sure that their passwords match so we already have the user service and we already created a way to find if a user is in our database or our internal in-memory array and that's just user service that find one passing in that username that was given right and remember this user is going to be undefined if that user wasn't there now to actually check if the passwords match we're just going to do a basic comparison here so if the user exists and their password in the database matches the password that we get from our parameter up here then we're going to return the user now you don't actually want to return the full user object because remember in our database that includes the password so make sure that you destructure the password out of that right so utilizing the rest operator here if we do result equals user right this result is basically equivalent to the user object except it doesn't have the password in there and if the user was not found or the password doesn't match we're just going to return null and this is going to signal that this user is not authenticated they don't exist in our application and then if you forgot to import the user service like i did here make sure to add that in all right at this point we're ready to start implementing our local strategy so within the auth folder we're going to create a new file in there we're just going to call it local.strategy.ts i'm going to add some code here and let me explain so first of all notice that we created a class local strategy which extends the password strategy from passport local right so this is sort of saying let's say you have different forms of strategies right so for example maybe you had a google auth strategy then you would replace the strategy with that okay so in passport the strategies are swappable you can kind of hot swap depending on what strategy you're trying to do your logins in in our case we're doing username and password which is what passport local is doing right that is our strategy so that's what we're extending in password strategy here in the constructor here you'll notice that we have a super here and this is ultimately calling the constructor of the strategy itself and typically the strategies usually will have a configuration object that you can pass in here so for example passport local specifically you can configure for example what is the username field right like in our case we're looking for the username itself but perhaps in your case you want that to be email so you can change this to be username field email and then whatever other strategy configuration you need to pass to that strategy usually you do it through this object right here right in our case we're just going to use the default so we're going to keep this empty all right the second thing that each of these strategies need to have is a validate method and this is effectively the the callback to verify callback of that strategy and it's going to get the details of you know as for example in this case the user logs in with the username and password it's going to call this validate method with that username and password and then it's our job to take that and verify that those are correct and remember we just created the odd service validate user earlier that takes in a username and password so that's exactly what we're doing we're going to validate the user right back in here we're checking if their passwords match and if it does we're going to return the user so it's going to come back here and we're basically just saying you know if we never found the user or their passwords don't match we're going to say they're unauthorized all right and then assuming the user is valid right then we simply return that and returning it here is going to give us access to that user object within the context of our graphql requests and you'll see this in action in a little bit now before we move forward i know someone's probably already asking like we already said this is not the best way to actually do security right we're just doing plain string comparison here so early on we're gonna do we're gonna add a to-do here to make this more secure all right so we're gonna come back to this so don't freak out all right so back in our local strategy here now that we've created this we need to actually sort of hook it into our application you know we basically need to register it so we're going to go back to our auth module and we're going to add a couple extra things in here so first of all our strategy is a provider so make sure to add that in here right and make sure to import that and then we're actually going to add another property in our auth module here which is imports and we need to add the passport module right so those are two important things that you need to have to get password working with your nesjs application is you need the password module and you need however many strategies that you have you need to have all those strategies registered as a provider in one of your modules in this case we're just doing it all in the auth module there's also actually one more dependency here that we need to bring in which is the user's module right and why is that it's because in our auth service remember that we're bringing in user service in here we're injecting the user service so we're dependent on that user service being exposed by something in this case it'll be the user's module that also means that in the user's module we actually need to make sure that the user service is in the exports so that it's not private it's able to be used by other modules so this should be users service all right so now that we have that in place we need to actually implement a way for us for our users to log in that's what we're gonna do in our auth resolver right remember this is still empty alright so within here we're gonna need to inject our auth service so make sure to import that and this is where we're going to implement our login query right which is which is a mutation now in order to complete this we need to specify a type here of what does our mutation return right so if we had a login response and we haven't created that yet if we had a shape like that we also need to have something in our auth service that actually logs us in and returns a form of login service so we need something like this dot service dot login right and there's some obviously key missing pieces here for us to get this working one is that we need to define what does this login response look like and two we need to implement the actual login logic that we have in our auth service so let's start with creating this login response so i'm going to create a new dto folder in auth and within that i'm going to create a new login dash response dot ts right and this is just going to represent the shape of what our response will be and to save you some time so you don't have to watch me type it i just created a class login response which is an object type and this is gonna bring back an access token right we're doing jwt when the user logs in we need to return an access token and then along with that is we're gonna return the user object you know that way when when a person logs in they have their access token they also have their user information all right so back in our resolver make sure to import that new dto all right so we define what we want to return but we also need to define the input itself which is we said we're going to log in with username and password so we need to define that shape as an input type so in our d2l folder we're also going to create a login user dot input and similarly i'm going to create something that looks similar to our other dto but it's specifically going to be an input type because it's going to be an argument this is going to be an input for our login query and that's going to require a username and a password all right so we need to provide that as an arg to our login in the resolver so we're going to do args login user input and then log into user input now make sure to import args from nesgus graphql and then make sure to import the login user input that we just created and then we're going to just forward this login user input down to our login method which is not yet implemented so we're going to do that next so in the auth service we're going to add a new login it's going to take in a login user input import that as needed alright so we said that within here we need to return an access token which we haven't implemented yet so let's just return the string jwt for now and we'll we'll go back and implement jwts here and then we also need to bring back the user and where do we get the user well we just do the same find one here and so we're gonna do find one login user input dot username right and that's gonna bring back our user and we're just gonna return that although just like before you don't want to return the user's password right so we're going to do the same trick we did here to destructure the password and we're going to return the result so we're going to do user result all right so at this point we've created a new mutation that allows a user to pass in a username and password right if you look at our graphql schema you'll notice that there's a new mutation there that has a login user input which is password and username but we didn't actually hook it up into our passport strategy yet right it's just taking in the username and password and forwarding it down to our service here there there's a missing piece we need to actually do this validate method here that is in our local strategy which if you remember is going to go in and check if the username and password is is correct so the way you would usually achieve that in ncs is through the use of guards so within our auth folder we're going to create a new file and we'll call this one gql auth guard and i'll add some code in here and let me explain so typically in if you had a rest application instead of graphql you can just use this auth guard from nsgs passport directly with the string of with the name of the strategy that you're using so in our case here passport local so for example if this were a rest controller you would just add in here use guards and then you'd simply add in off guard local here from from that passport so the built-in off guard from passport so that's typically what you would do if this was a you know a typical controller rest application but because we're doing graphql there's a little bit of an extra step so the reason we're doing an extension here is because passport local is actually designed to work for a a typical rest application not really graphql right so it's a slightly different interface right in a graphical application usually you have this context but in a rest application you usually have you know just like a request object right so in order for passport local to work it's looking for this request object that includes the username and password in the body right but by default it doesn't have that because it doesn't sort of automatically wire it up from the graphql context to the request body because in graphql you don't have this typical request object you have the context the graphql context so what we're doing is we're basically mapping we're taking that context and we're creating the request object there that the passport local strategy is expecting to have and we're adding in as you can see here we're taking the args the input from our graphql context our graphql mutation which is going to have our login user input which includes our username and password and we're just mapping that to request.body which is where the local strategy is going to look for those credentials so you need this for this passport local strategy to work for the graphql context right so this is a major gotcha that if you don't have this it's not going to work so make sure to pause the screen if you need to and copy this code and then let's move from there all right so now that we have this dql auth card which is an extension of auth guard local we can now go back into our auth resolver and add in our dql auth guard in here and make sure to import that so at this point we have everything that we need to log in if we go into our terminal i'm just making sure here that there's no red errors we didn't miss anything graphical playground i'll open a new tab in here and we now should be able to create a new login mutation and remember that this takes in a login user input which we're just going to put in like this and what's going on here right this is so so that we can provide the variables in the query variables panel over here so for example we can do input and whatever we pass in here is gonna go over here and it's gonna get passed into our login mutation right so this is where we're gonna have username and password and then remember our login allows us to get the user back as well as their access token so if we hit play here it's going to give us back a 401 unauthorized and why is that because we just used a dummy username and password in here right if we look into our dummy users database we've got a username or password that looks like this so we're going to try that so i'm going to do marius and not secure and if i hit play here now i get back me the object from the database that has my name on it and our fake jwt right so we effectively figured out how to do logins right so we're not able to log in so let me summarize the flow because i know that was a lot and it's probably very confusing to follow all right so let's try to follow the flow here right so when we send that mutation on the graphql playground we're effectively trying to get down here right but before it actually gets down to this actual handler down here it first needs to go through the guard right so if if you're not familiar with guards in suggest you can just think of them as middlewares right there they're specifically a middleware that's in charge of checking if a user should move forward or not to the actual handler so when we call the mutation with a login user input which is our username and password it's first going to go through the guard and our dql guard is mapping the the context to a request object right which is our again our username and password it's mapping it to a request object that the local strategy is able to parse out and provide to this callback over here so once it's able to see the username and password in that request object it's going to trigger validate with that with those credentials so now we're in here where we actually validate the user so within this odd service validate user we're looking up the user in our user database we're checking does that user exist and does their passwords that they passed in do they match so if those two things are true we then just return the user itself right so this just bubbles up back up to returning the user from the strategy and what happens when you return an object from the strategy that becomes the uh the user context that you have in every request that's how we're able to tell who the user is in any graphql request right so once it sort of goes through that then it's able to actually continue into our login handler here which calls this guy to return our the user itself which we did another find here and then our jwt which we're actually gonna complete in a little bit here but that sort of completes the the entire flow if you're able to follow all right so actually now that we're talking about this flow i'm realizing that like i said from the strategy when we return the user here it becomes part of the context so technically we don't really need to do this query this user query again in the in the login method here in the auth service we can actually just get back the user from the contacts so in our login resolver here if i add context from sgs graphql let's call that context the user object that we return from the strategy through the validation is actually inside context.user in the same way that in express if you're doing the same thing it'll be in request.user if you're doing rest in this case with graphql you have context and the user is in context.user so i can actually just do context.user pass that into login and then allservice.login will change this to actually just taking in user so let's update this to taking the user entity let's clean up our imports here and we don't need to do this find one anymore because we already have the logged in user from the contacts we don't need to find that person anymore we still have that record handy and this will be exactly the same as before so back in our playground if we try to do the same exact thing and log in we'll get the same exact response as before all right so hopefully that was a good summary of how the flow works at this point we just need to further improve it so that you know it's a little bit closer to what it would be in a real application right we did a lot of like to do's here that we need to go back to so let's start with this jwt piece so ideally we're able to create a new jwt that we return as part of their login right they log in they get back at jwt and then moving forward we check the request if they passed in that access token to see if they're already logged in or not right if they're authenticated that way they don't have to keep logging in at every single time alright so before we do that actually we don't need this anymore as well because i remember the user context that we return does not have password in it anyways so we don't need to do this structuring trick that we did earlier because we already did that up here right so this user already has these passwords stripped out so we just need to return the user directly all right now we can actually start installing our stuff that's going to allow us to do jwts so just like we had passport local there is also passport jwt right so we're going to install sjs jwt and password jwt so what those two things are nest jwt is really just a utility package that has a lot of the useful jwt stuff utilities that we are going to use and password jwt is specifically a another password strategy remember you can use one too many strategies but this one is specifically we're going to use it to protect our routes so that it it's looking for a jwt and it's going to verify and validate that jwt and which we'll see this in in just a little bit here one more thing we're going to install the types for password jwt just like we did with local all right with those things installed what we're going to do here is in our auth service we want to actually pass in or create a jwt that we're going to return instead of this dummy string that we have here so to do that we're going to import jwt service from that thing we just installed and we're going to have this be injected through the constructor just like everything else right so we're going to do jwt service we type jwt service all right and so we're going to replace this fake jwt string to be a real jwt and we're going to do that by doing this.jwt service dot sign and we need to provide it with a payload you know appeal the payload for actual jwt and we're going to do username user.username and usually in jwts you have sub to represent the sort of the identifier of that the id right so we're just gonna use user.id for that all right so it should look like that now for this to actually work we need to configure the jwt module so we need to go back into our auth module and we're going to add a new import here which is the jwt module and we're going to register it and we're going to provide a little bit of configuration here so first we're going to provide a sign-in options so this is where you can configure things like when does that jwt expire in our case we're just gonna do something simple 60 seconds all right the next piece of property here that is important to provide is a secret and the secret accepts a string that is a secret all right it's something that you need to keep secure right now i'm just going to return i'm just going to give it a hard-coded value let me remind you again that this is not a production solution all right this is a tutorial i'm trying to teach you the fundamentals you don't actually want to hard code this value like this i would suggest that you maybe pull it out of environment variables so that you can pull this out out of process dot env and then you know give it some kind of key like you know jwt secret or something like that right so again don't freak out a lot of people freak out when i do stuff like this and tutorials and not do it the production way right it's your job to make this production ready um my job here is to just teach you fundamentals all right so we need a secret string here keep it secret i cannot stress that enough all right so at this point once you have that jwt module registered and in your auth service we're doing this jwt sign if we go back to our playground and log in again now it's actually gonna give us an access token like a real jwt access token now if i go i can copy this and go into right so if i paste in that access token into jwt io you can see that it's very easily decodable you can see that we have the username and id in there the sub that we passed in but it says that it's invalid signature because there's something missing right it doesn't have our secret key that's why the key is extremely important to be hidden because that's the thing that we add in here to verify that this is correct right so if i add hide me in here which is our super secret key now it says that the signature signature is verified but without that you can't verify the jwt so again keep that string as secure as possible and make sure it is not exposed in any way to your clients all right so there's a quick jwt lesson for you now we're able to create an access token the next thing we need to actually do is protect one of our queries with a jwt we want to say that our if we go back to our user resolver you know perhaps like we said earlier we want to protect the find all to require a jwt we don't want anybody to simply get access to this right so we want this to respond in a way that without the jwt it'll just fail all right so how do we do that so remember we also installed a passport jwt strategy that's what that's exactly what it does is it's able to parse out the jwt from the request and it validates and verifies it so in our auth folder we're going to add a new jwt jwt.strategy.ts and just like before i'm going to add in the code in here again we got a class jwt strategy we're taking now the strategy from passport jwt instead of passport local like we did before and we're extending that so similarly like i said earlier if there's any configuration that you need to do for that strategy you do it in the super here so in our case we're saying here that we're expecting the token to show up in the authentication header as a bearer token we're going to say don't ignore the expiration so meaning our token we said expires after 60 seconds if it's 61 seconds or more then it's gonna fail it's gonna say that's not a valid token anymore because it's expired and then again this is where we provide our super secret key that again i have to remind you please hide that hide me again as the name suggests and then what's gonna happen is as it extracts that jwt from the auth header it's gonna decode that and it's gonna call the validate callback with the payload right so payload here is the decoded jwt it's sort of like what the jwt.io is doing for us right and in that case we just take out what'sa decoded and return back a object that again becomes our user context just like before right just like how the user the local strategy is returning a user object our jwt strategy here is also returning another user object which again is going to be saved in our context under you know dot user context.user all right so now that we have that strategy just like how we had a guard created for our local strategy we need to create a guard for our jwt strategy so again in the auth folder i'm going to create a new jwt auth dot gord all right and just like before we're going to create a new class jwt auth guard that extends off guard jwt again i kind of explained this before right we're extending the built-in auth guard and we need to sort of manipulate it so that we're translating it to work in the graphql context if this was a regular rest application in that graphql then you can just use the auth card directly but in this case we have to do this translation so that we're able to use it for graphql pause the screen if you need to copy this this is an important gotcha otherwise it's not going to work all right and then before we forget remember that anytime you create a new strategy you need to register it in a module so just like we registered local strategy in auth module we're going to register jwt strategy as well all right so now for us to protect the users query in our users resolver if i were to add a used guards jwt guard in here and add our missing imports right so used guards from nsgs common and jw off guard from our auth module let's clean up our to do here because that's exactly what we're doing right let's see what happens now if you try to query users so back in our playground in our first tab here we're just going to do our old users query and if i hit the play button here it's going to say you're unauthorized because you don't have the access token that's because it's now with the guard looking for the jwt and the http headers right so in the http headers on the bottom here we need to add authorization and we need to provide a bearer and we need to put in our token in here and where do we get our token from from the login so we're going to log in again using it bring us back an access token that was signed using our super secret secret copy that we're going to paste that in here and we're going to run this request again and now we're able to pull back the users so let's do a quick summary of what happened there right we performed a query of users get users right before it gets down here again think of guards as middlewares it goes through our jwt auth guard which just like in our local guard it's gonna translate the request so that this is something that the jwt strategy is able to understand and it's it's gonna be able to invoke it right so that guard ultimately invokes this jwt strategy it goes in here it extracts the jwt from the auth header as it says here it'll check to make sure that it's not expired already and it's going to make sure that that the jwt is valid by making sure it was signed correctly using the same secret key that we have right that that's how it verifies that it didn't just randomly come from any other you know application that generated the jwt it's actually a jwt that we generated from our login and once it goes through all that verification it then calls our call back with a payload of the decoded jwt and that completes the flow it this returns a user which makes it available to the context and then at that point we're past the guard and we're now down in this handler our resolver here and it's going to do the find all and return our users array all right so i hope that summary makes sense if you ever have any logic that depends on who the specific user is right remember that you can pull in the context like this and the user will be available in context.user the user that we decoded in our jwt strategy here just like we did in the local right so you can use that context context.user to access the user's data and within this jwt you know we have a pretty simple object in here of uh you know it's just a user id and the user name but if you for example have a more you know larger more complicated user record in your database you know you can do a get user call here just like how we do it in the local strategy right you can do a database call and get back your full user object and then it's up to you what you return over here to make it part of the context right and that context will be again it'll be available in the user context site user all right so we pretty much were able to login we're able to protect our queries with the jwt there's just one missing thing right we said we're also going to do sign up which we probably should we probably should have done first um but i think it it's easier to teach it this way because you're able to see sort of how the logic flows but now that we have all of these things in the pipeline all right so here's what we're going to do back in our auth resolver just like how we have a login here we're going to create a mutation for sign up right and this is a mutation which we're just going to return the user all right and we need to have an augs here right we need an input which is really if you think about it the registration is pretty much the same thing as like a login except that the password can be anything right so we also need to ask for username and password so really we can just reuse this uh login user input in here if we want to you can of course rename it to something else if you want but let's keep it simple here and what we're going to do is we want to take that uh username and password and effectively store it in our database right so we're going to return a this dot service dot sign up and we'll pass in that login user input and similarly as before we didn't implement sign up so let's go do that in our auth service so we'll create a new signup method here that takes in a login user input which again has username and password and first thing we want to do is probably make sure that this user does not already exist right so let's do a a real quick check of await this dot user service let's do a find one and let's make sure that the username that is passed in does not already exist in our database so if we actually find a user let's just throw an error in here and say user already exists now if you're using a real database what i would recommend is that you actually just use like the unique constraint on the user id or the username and that will automatically make it that at the point that you try to insert it's actually just going to bubble up an error saying hey this this already exists then you don't need to do this explicit query right in our case we have a fake database so we gotta have our own fake way to do a unique constraint so if the user wasn't found that means they're new so we can just do return this.userservice.create alright so we'll just spread the login user input in here which has username and password we have an error here saying our create user input is is expecting an id that's actually a mistake i think so in our user service we have a create which is expecting a create user input we're actually going to remove the id field in here because we don't yet have that that's being created in the method itself so it's not something that's being passed in we're doing that down here right in a database again usually you can do auto incrementing ins so this user service i create is gonna add to our array basically right so it's calling this create method in our user service it's going to push to the array and it's going to return that user with a new id in it all right so that basically is our sign up although i did say that you know we want to make this a little bit more secure we don't actually want to store passwords as plain strings in our database right we had a to-do in here so what we're going to do about that is we are going to install recrypt which is the typical package you would use for this types of situations right i recommend you read about this you know from the the package itself and there's this really good article how to safely store a password from coda hail you know this goes into a lot more detail and it's a little bit out of scope for this video so i'm not gonna take a lot of time explaining what recrypt is and why you need to use it but i recommend you look into this article you know maybe i'll do that coverage in another video but basically what it's gonna do for us it's gonna hash our passwords so that we instead save the hash instead of saving the plain string right so in our sign up here we now have the crypt module installed we're going to pull that in import start as recrypt and down here if the user doesn't already exist we're going to create a hash password and we're going to do that by doing decrypt dot hash and then we're going to pass in the password that the the user typed in right the plain string password we're gonna hash that and the hash is actually what we're gonna save in our database right and just so you can see this happening we're gonna add a console log on our create here just so you can kind of view what our database looks like right so we're going to log out so we're going to log out our disk at users and we'll see that in action in a little bit just quick check in our terminal make sure nothing is broken in here and we're gonna go to our playground and simulate a sign up the sign up like i said is pretty much similar to a login so we're gonna copy that mutation actually and let's just rename it a little bit and we can pretty much also copy this uh input in our query variables here and our signup only returns the user not the access token so let's do username and id and what i actually expect to happen here is it's going to fail because this username already exists right so when i hit the play button there it'll error saying user already exists so let's do a fresh user let's just do add a two here and change the password to something more secure i hit play here and that's gonna sign up add to the array with a new uh username and if we check our logs like i said we're logging out the full array right notice that it added this new object with that new user with the hashed password so that's significantly much safer than storing the plain string passwords like this right but if you're following along we need to fix something in our code so that when we do the the password comparison we're able to log in with the regular non-hash password but we need to be able to compare that with the hash password right so for example if i try to log in with my new credentials here so i'm going to copy this from sign up to login right so i'm trying to log in as my newly created user and i hit play here it's going to say it's unauthorized because what's happening back in our auth service remember it you know through that flow of logging in it eventually gets to validate user and it's doing this this user password check so it's it's actually comparing the the password that we typed with the hash password and it's failing right here because they don't match so what we need to add in here is we need to bring in recrypt again to do that comparison for us so we're going to do valid equals await recrypt.compare and we're going to pass in we're going to compare our the password the plaintext password that's typed in from our mutation and the user.password which is the hash password in our database and it's going to do the comparison for us and if it's valid then we move forward so we're going to replace this comparison with the valid and i guess there's a scenario where the user doesn't exist right so we'll do a question mark there now if we go back to our playground we actually need to do that sign up again because remember this is just our database is just an in-memory array so every time we type in code it's actually clearing out any of our new signups so i'm going to sign up again in here just to make sure that's actually created right it's gonna do the hashing for us and then now if i go back to login with those new credentials right with the plain text password it's gonna do the comparison for us in uh with the hash database password and if i hit the play here it's able to log in and get back our you know our user as well as the access token all right so now that's a little bit more secure really the only thing that can be improved upon here i think is like i said use a real database that's out of scope for this tutorial and then finally make sure that you hide the secret the jwt signing secret hide that and put it into environment variables right that should be pretty easy for for people to do and then i believe that's it right so just to summarize we've implemented how to log in well first of all we've implemented how to sign up which adds new users to the database and takes their password and hashes it puts saves that hash password in the database and then we're able to log in and actually make sure that the password that the user provided matches that hash password in a database and if all of those are good the user exists we're able to log in and we'll return an access token and then that access token can be you know from a client's perspective can be stored somewhere else and then they can attach that to their requests within the auth header right like this which we then sort of extract from any request that is requiring that jwt to exist right it's acquiring that the user is logged in we're able to extract that verify that that's a valid jwt and then from there we update our context same thing as well for the login we update the context with the user object and then we can access that user's information from any of these you know resolvers right so we implemented login sign up and protection using jwt and i told you what are the things that you need to further improve from this tutorial and that's it i think that's it all right guys i think that ends my recording here hopefully that was a good tutorial for you uh please let me know what you think in the comments i very much appreciate any kind of feedback you have even if it's a bad one i'll read it i'll respond to it and if this was in any way valuable to you please hit that like button and subscribe i got more stuff like this to produce in the future anyways with that said hope you have a great day and i'll see you in the next video
Info
Channel: Marius Espejo
Views: 25,905
Rating: undefined out of 5
Keywords: nest, nestjs, nestjs tutorial, nestjs auth, nestjs authentication, nestjs authentication jwt, jwt, jwt authentication, jwt token, jwt security, express session, passport js, node passport, passportjs, authentication, node, passport js jwt, passport js tutorial, passport js google, passport js react, nestjs passport, nestjs passport jwt, passport local strategy nodejs, passport local strategy, nestjs passport local, nestjs guards, grapql, graphql auth, graphql jwt, graphql login
Id: XPSSgAPjTb4
Channel Id: undefined
Length: 53min 18sec (3198 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.