You don't need passwords anymore! NestJS passwordless magic link authentication

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
everything about how passwords are kind of terrible you visit a website for the first time in just to get started they ask you to come up with a new password and it's just not fun and coming up with secure passwords is often a struggle for a lot of people so what ends up happening is people use the same passwords over and over again making them basically not secure because if one gets compromised they're all compromised either that or they have to pay for a third party just to manage their passwords for them and if not people also often forget what their passwords are so that means you also need to implement the ability to reset passwords so what if instead we just got rid of passwords how would we pull that off well we can send magic links basically we'll email a specific link to a user that only our server can generate and expires after a certain amount of time that means the user just needs to provide their email address they'll get in their email they click the link and they're in so that's what we're gonna do in this video we're going to implement a passwordless authentication and nest.js using magic links and then I'll also show you how to keep track of the user sessions using jwts alright so as usual with my videos we're going to create a new application from scratch so that you can follow step by step exactly how to do this so I've got a terminal open here on my desktop I'm using node 18 npm 8 and then make sure to install the nest.js CLI globally if you haven't already and then we're going to use that to create a new application so we're going to do Nest new and then a project name I'll just call it passwordless pick your package manager of choice it'll start installing for you and then once this is done we'll open it in vs code actually while we're still in the terminal go ahead and CD into your new directory and then we're going to install a couple packages now I'll talk about each one of these dependencies as we actually use them in the tutorial right now we can open this in vs code so we're going to do next generate module users and that's going to create us a new users module and it's also going gonna hook it up to our root app module and then we'll also do Nest generate service users which is going to add in your user service inside our users module within the users directory we're also going to create a new file user.entity.ts and this is just going to be a simple class that will represent the shape of our users right so it's pretty simple we're just going to have an ID an email and a name all right next open up user service and here we're going to create a mock database of our users so we'll create an array called users which is going to have a type of user of the entity we just created and then we'll just fill the same with a couple mock users Mumbo and Dumbo and that'll be our initial users database alright to wrap up the user service we're just going to add a simple method here that allows us to find a specific user based on the given email so we'll have a method find one by email that takes an email string and simply iterates through the array and finds the one user that has a matching email right so this pretty much leaves the groundwork for us to be able to tell you know which users are already registered and can actually log into our application now this user service is going to be used in other places later on in this tutorial so we have to make sure that this is actually in the exports array of our users module all right now we need to start laying down the foundations for our authentication logic so we're going to create a new module for auth and in fact we're just going to do Nest generate resource auth select rest API for the transport layer and know for the crud entry points this is basically going to do the same exact thing as users except now we also have an auth controller all right so before we write some code let's actually try to talk about the design of our API what are we trying to implement so if you're familiar with messages you should know that controllers are in charge of defining routes for application so if it has auth here that means that's a prefix for all the routes inside the auth controller so let's say that we're trying to implement a post slash auth slash login and the request body of that is going to contain the email inside it now our expectation is that when you call this what it's going to do is make sure that you're a user and then send you a magic link and then imagine that a user gets that magic Link in their inbox and then they click on it then we need some kind of callback API that intercepts that click so we're going to do a get slash auth and let's just do login slash callback and then part of this request is actually that it's going to have the token as part of the request query and then the idea is you know we take that token we make sure that it's valid and that it's not expired and then we create a JWT that we can send to the consuming client and then finally we respond with a generated JWT as basically the access token for our application now you might be wondering where does this JWT access token you know come into play into our application basically we're going to use that to keep track of user sessions and we're going to expect that any route that is protected is going to check for that access token in the headers so for example imagine that in our app controller we can add another route in here and we'll just call this get protected right so this is basically defining a route on slash protected and we're saying that we need to require a JWT here otherwise we should reject the request right so we'll add it to do here to make sure we'll come back to this later in the application but for now let's go back to our auth controller and Implement uh these routes so starting with login we're going to do Post login and then the method itself can be called login and then similarly we'll do get login slash callback and callback clean up our comments a little bit and then make sure to import these decorators actually from an SGS common all right and then we'll actually go ahead and Implement these one at a time all right so let's quickly talk about how we're actually going to generate these magic links and you know send them to users earlier if you remember we installed passport which hopefully you're familiar but basically it's like it's like an API that allows you to plug in different types of authentication strategies so you can see that so for example maybe you're trying to do you know old school user username password or password list or oauth right you can swap the different strategies for your application while still using mostly the same API so that's the benefit of passport so what we're going to do is we're going to use a very specific strategy for passport that will allow us to do passwordless authentication with magic links so remember we installed passport magic login actually noticed that the author here is Max who is one of the authors behind style components if you're familiar with that in the react ecosystem so at a high level what this strategy allows us to do is if you give it an email it's going to generate a custom login URL for a user you can see for example here they have slash auth blah blah blah token and this token is actually a JWT which is a pretty clever solution if you think about it because jwts are designed to be you know signed using a specific secret that only your server knows and then built into jwts is also the ability to you know add a specific payload in there like user information plus it also has in its specification the ability to add an expiration to that JWT so basically everything that we need for a magic link right the ability to expire it after a certain amount of time the ability to make sure that only our server can create it and also the ability to encode specific information about the user in that URL everything is basically handled for you within a JWT right so basically we don't have to worry about storage which is really nice all right so now we're going to go back to our code and basically we're going to get this strategy configured in our application so what we're going to do is in the auth directory we're going to create a new file call that magic login dot strategy dot TS and this is going to be a class called Magic login strategy which extends passport strategy and then in here we actually need to pass in the strategy that we're trying to implement so we're going to import the strategy from passport magic login and then pass it in here and then we're going to make this an injectable so From nest.js's perspective this is basically a provider and since we're basically just extending the strategy that means that we can configure it via the Constructor So within the Constructor we're going to add a call to super here and then we're going to pass in the options for our strategy within that and if you need a reference to understand what are the properties that you need to pass in you know just go back to the documentation of any strategy that you're trying to implement for example in our case here we need to pass in secret callback URL send magic link and verify so first let's add a secret this is basically what's used to encrypt your information into the JWT and only your server should notice so this should absolutely not be exposed publicly in any way also it shouldn't even be hard-coded in a code like this we're just doing it here for Simplicity but make sure to you know move this out to environment variables for example so that it's not just hard coded right again keep this secure this is very important next we can pass in JWT options so like I mentioned earlier the token that we're creating for our magic links is really a JWT and you can configure the options for that JWT for example at what point does it expire so that basically means if your token is expired your whole Magic URL magic link is expired so we're going to Define an expires in and for this I recommend something you know fairly short-lived like five minutes 10 minutes something like that you definitely don't want it to be available for like an hour so let's do expires in five minutes next we're going to define a callback URL and we said that that's going to be on slash login slash callback now in production you might not necessarily be running this on localhost 3000 so make sure to change that to whatever you end up using in the real world next we're going to define a send magic link option which has a signature like this takes in a destination and an href the destination is basically you know the email that's provided so this is effectively where our magic link is generated and this is also where you can send that into an email now how you send that email is really up to you we're not going to cover that in this tutorial that's a little bit out of scope but you got a bunch of options to to pull that off so for example maybe you're using AWS they have an email service or maybe you're using some other third-party service or if you have like an SMTP server you can use the node mailer package right so there's a bunch of different ways that you can send emails again that's a little bit out of scope for this tutorial so what we're going to do instead is I'm just going to add a logger here and then we'll just kind of mock that send right so we'll do this.logger dot debug and we'll just output that to the terminal we'll say you know sending email to destination with the link href and then that's gonna basically simulate for us as if we sent an email to I don't know some users Gmail account but it's going to show up in our terminal so we can just access it there so the last option to Define here is a verify callback which is invoked when you know the user clicks on the link and then we our application looks at the token verifies that it's correct after that the verified callback is invoked so we need to provide a verify here now let me talk about this real quick and necess.js typically if you read the documentation if you're trying to configure strategies what you usually expect is that within your password strategy you have a validate function which is supposed to be equivalent to basically what the verified callback is and if the strategy that the library was written correctly this method is actually going to be invoked automatically however that doesn't happen for for this magic login Library so we're just calling this dot validate with the payload that way we just forward it down here that way we can follow the typical messages pattern with passport which usually has the validate method down here and you'll see that later on too when we implement the JWT strategy so what this payload is is basically it's the think about it as the the JWT that we get in the URL if it's valid it gets decoded and whatever information is in that JWT in our example it's going to be the destination or the user's email that's going to be inside the payload so that's going to be provided over here right so you can imagine that this payload has a type that has destination in it that's a string now as the name implies we need to do any final validation on this payload before we let the user actually continue with their authentication flow so in here we need to validate the destination a risk of the email to make sure that this is a valid user now this logic of basically having to validate that the user exists and is a user of our application or otherwise throwing an error that's logic that is going to be reused in a couple different spots in this tutorial so we're actually going to centralize that logic into the auth service and remember that the auth service is a provider so that means we can inject that into the Constructor here so we're going to do private auth service and make sure to import auth service and then what we're going to do down here is basically we'll just do this that odd service validate user we'll pass in the email in there and then if it is valid it's going to return us back a user otherwise it's going to throw an error and this is giving us the red error here because we haven't actually implemented that so we'll do declare method validate user which is going to add that to our auth service but obviously we need to implement it all right so to implement this logic remember that we already built the logic around finding a user based on their email so that's in the user service so we're going to inject that provider into this service so we're going to do private user service of type user service to make sure to import that and remember that destination is really the email so we can just rename that for clarity and then we'll use that to look up the user with that given email and if we don't find a user we're going to throw an unauthorized exception import that from Nest just common otherwise if we did find a user we'll just return that all right so pretty simple logic now real quick in order for this injection to actually work we need to make sure that the auth module is importing the users module and then remember that the user's module is exporting the user service so that's how we're able to basically use a service from a different module all right so with all that in place going back to our magic login strategy basically we just need to figure out how to invoke this strategy within our login route so that effectively the send magic link gets invoked and then produces us our login link now remember that this is an injectable so it's a provider so we also need to make sure to register this in one of the modules so we'll do that in the auth module make sure to provide that in here in the providers array also make sure that you're importing the local one the one that we created not the one from the library so this is basically our own extension with the configuration in it all right with that in place with the strategy registered like we said earlier we basically need to invoke the send logic and the way to do that is if you look into the class of the strategy you'll notice that it actually exposes the send method here which takes in the requests and the response and that's what ultimately triggers these send magic link that we provided down here and then it'll automatically fill in with the information of the destination and the href so what we're going to do is we're going to go back to our auth controller and now we need to actually truly implement this login route first of all we need to inject the strategy in here to make sure to import the magic login strategy again the local one that we have and then we say that we basically need to invoke the send from that class which requires the request and response which you can actually get access to from nests using a couple of decorators so we're going to do Rec and rest from that's just common and we're just going to forward that down here at this point we're actually ready to run our application for the first time and make sure that what we have so far works so in a terminal Run npm Run start Dev and you should expect no errors here if you've been following along now while the application is running I'm going to open up the Thunder client which is sort of like a postman but you can use it in vs code and you should be able to make a request to localhost 3000 and that's going to return hello world that's just from the default code that generated now we said that we also have a slash auth slash login which is a post let's try to make a request to that it should say that we need to actually pass in a Json which is correct because it's expecting an email so within the body we need to provide destination and then some email address and now when we hit send here you might have a error in the bottom here and if we look at the terminal you'll notice that it's because it's airing out on this descent the underlying send code which is expecting the send magic link to actually return a promise because it's going to do that n over here so to fix that that's pretty simple we just go back to our send magic link inside the magic login strategy and just change this to async is correct because you think about it if sending if it's sending an email that's an asynchronous operation all right now we should be able to again go back to our request post slash odd slash login hit send with a destination of some email and we're gonna get back 201 created and if we look in our terminal we should be able to see that log that we created earlier that says sending email to this address with the link and you can see that it has that full URL that we have slash login slash callback with a token attached to it now if you're paying close attention you should actually expect that this is the wrong Behavior because we said that this should only work for users that are already registered in our application and within our mock users that's not even one of the emails that we have here right so we actually expect that to fail now what we can do to do this validation up front is to Simply extract the email from the request we can use the body decorator here and then since we already have the odd service injected in here we can just call our validate this.aut service that validate user and that should throw an error if the user was not found even before it runs the send logic so if we make the same request again this is actually going to come back as a 401 unauthorized but if we switch this to one that is a valid email Mambo email.com hit send there then we're back to it being okay right so now we kind of have a little bit of validation in place now this is assuming that you're not using the same method for signups you could if you wanted to add sign up logs you can hear like for example maybe you just want to get the user up and running very quickly with just the email that's fine you can add sign in logic here but that's a little bit out of scope for this tutorial now there's one more piece of validation here that would be really nice to have which is ideally if the email is not even a correct email pattern we should just send a 400 request back and don't even try to validate it right so for example if I you know send something gibberish like this this should just straight up fail instead of just saying unauthorized it should tell us hey that's not that's not even an email and that's pretty easy to do basically we're just going to introduce a dto file let's call this passwordless login .dto.ts and then we'll basically we'll just create a class that represents a request body so that's going to have destination in there and then we're going to use class validator remember we installed this in the beginning and we're just going to annotate this and say that this is supposed to be an email now if we take this and go back to our auth controller and provide this in here as a type to the body all we need to do now is in the body decorator here we can add new validation pipe this is one of the built-in middlewares in Nest that allows you to you know validate your dtos so now if we go back to our request with the gibberish email if we hit send here now this is going to return a 400 bad request and it'll say why right so this will be very useful for a client application to do the email validation for you and then just checking again if we do provide a valid email address and an existing user you know it should get us back that 201 created right so at this point if you remember in the terminal it is logging out that callback URL that we basically are going to send via email to the user now we need to actually invoke our callback URL to make sure that this token is valid right so what that basically means is we go back to our auth controller and then we need to actually implement the logic for this callback now most of the logic that we actually need here is basically already contained within this strategy right like we said that it defines when the token expires and then this also has the validation logic in here so basically we just need to find a way to invoke the strategy for our controller and the way you do that with Nest is usually through the use of guards so we're going to do use guards auth guard and make sure this is the one from nest.js passport and this takes in a string and we're going to say magic login and what is that basically this is saying hey passport get me the strategy that has this name which is the one we're using here and it's going to invoke that strategy before it actually goes through and finishes the rest of the request and then finally once this piece is done we would just return the user right and that user becomes available inside request.users so if we take the request here again you'll find that the let's just return the user if we do wreck that user this is actually going to contain the user that were returned down here all right and before we test this out actually I have a very small typo here this should be login slash callback not logic all right so let's go ahead and test our login again let's do uh post on slash login destination Twitter email should go back a success and then remember we're printing out the login URL inside the terminal so you should be able to copy that and make a request to it you can just paste it in the browser in my case here I'll just open up a new request and then make sure that's an HTTP get hit send and notice that we got back Mumbo our user record all right so hopefully that makes sense let me walk through again what what is going on so we log in the strategy creates us a magic URL which is this which contains a JWT and that JWT has in it encoded the user's email which is the destination so it's basically this this is the payload just encoded into the JWT and then when we make a request to our callback it parses out that token out of the request it validates that it hasn't expired yet and it also uses the secret to make sure that you know this is valid it has a valid signature and if all those passes it decodes the token that we have here and then calls verify with the payload this so the payload here is the decoded token that we have in our URL which right now all it contains is the user email right so all it has is the destination so in order to get the full user record we basically do another lookup and then we simply return the user and the way password and Nest is hooked up to work basically what you return here becomes automatically provided within the request.user here so that's how we're able to access who that user is and now this user is effectively logged in alright so at this point so far all the codes that we wrote is what's allowing us to create and send magic links and if that's all you care about you can pretty much stop right here but real quick I'm going to show you how to set up jwts so that we can keep track of the user session that you know we know that they're logged in using an access token right and to do that basically what we really want to do is after they do the Callback here we don't necessarily want to return the user record what we really want to return here is a newly generated access token or a JWT that will basically allow us to say hey you're now logged in you can use this access token to access any of our protected route so instead of just returning the user here what we're actually going to do is we'll do auth service dot generate tokens and we'll pass in the user in here the request that user and this is not implemented yet so let's do the declare method again all right so here basically we're passing in a user and then we need to generate a JWT now how do we go about doing that so let's go back to the auth module and we're going to register a couple things in here in the Imports make sure to add passport module which is going to be important later and then for right now we actually need to do JWT module which is coming from JWT remember we also installed that earlier in the video this is the module that basically is going to expose us a a method that creates and generates our JWT so make sure to do register here and this is where you can configure the various options for your JWT generation so for example you also need a secret in here um again same thing make sure you're pulling this from environment variables don't expose it to your clients keep it secret for this tutorial I'm just going to provide you know a simple string and then finally you can also provide a sign options here which is where you can configure things like expires in sort of like what we use in the magic link strategy it's pretty much also using the same Library behind the scenes so here we're going to say that this one will last for an hour right so our sign in the JWT that we use in our login links only lasts for five minutes but the access token once they're actually logged in will last for an hour or however much longer you want this to be you know so this is basically the length of your user session now what this JWT module exposes to our auth module here actually is a JWT service that we can use to create tokens so back to our auth service we're going to add dependency here in the Constructor private JWT service which has a type of JWT service from sjs slash JWT and then this generate tokens method actually should be below the Constructor here let's move that down here and then now all we need to do is construct some kind of payload and you can put whatever you want in here typically you need to provide a sub for the ID so we'll do user.id and then let's just add the email in here it's up to you what you want to add to the JWT payload you might want to keep it small though all right so once we have some kind of payload now we can just create our JWT let's do return access token and then we'll use our new JWT service to sign our payload and this basically should return a JWT so let's go back to our Thunder client we're going to need to log in again and then let's get back our new login link and let's replace our request here which was expired already hit send and now we're getting back an access token all right so we're pretty much almost done the only thing left to configure is remember we have this to do here in our protected route that says hey require JWT right like right now if you make a request to get slash protected it just lets you in because it's not protected in any way now what we need to do from here is just like how we added a strategy for you know our magic login we just need to add a strategy for uh authenticating a route with jwts that's what passport JWT is for which is also another thing that we installed so the process for that is very similar to what we did before which is basically kind of like how we have the magic login strategy here we're going to do the exact same thing except we're gonna do JWT strategy dot TS and in the interest of time I just pasted the code in here you can pause the screen to copy it but it's the exact same thing that we did before we're going to do JWT strategy extending the strategy from passport JWT and then within the super we're gonna configure a couple things in here basically we're going to say that the bearer token the access token that we have expect that to be in the auth header make sure to not ignore the expiration and then we also pass in the same Secret in here this needs to be the exact same secret that you provided in the the JWT module over here if you remember so you can kind of Imagine That this strategy is going to take the token and then it's going to check the signature of that to make sure that is it was correctly signed using the same exact secret right so that's why the secret here needs to be the same as the one that we use to sign it because this is the one that we're going to use to verify it and then like I said before typically what happens with strategies is that it's automatically going to invoke the validate function here with the payload right so imagine we have we have the JWT that gets verified it gets decoded and once decoded the payload of that is going to be passed into validate right it's very similar to what we did earlier with the magic login except that we don't have to do this this manual this that validate call it's going to be called automatically for us and then similarly in here we're just verifying that the user exists you know maybe because you know what happens if the user got deleted in between the requests so we're just going to validate the user again and then return that user and then remember when the when the user was returned here it becomes available inside request that user so with this in place just like before we need to register this within a module so go back to author module add it to your list of providers here so you should have JWT strategy and then at this point we should be able to go back to our root app controller and in here we can add use guards it's like before auth guard from an sjs passport pass in JWT and then just a reminder again you know whatever you return here inside the validate effectively becomes exposed under request set user so if we do right here again and change our return statement here to be maybe let's return the name and say you're in let's go ahead and test our application just to do the end-to-end flow again let's go to auth login hit send there go to terminal get our login link and then we'll do a get on that callback if you get back an access token in the response now we're going to take that access token we're going to go back to making a request to get slash protected and remember if we hit send here it's going to say 401 unauthorized but if we go to auth provide this as a bearer token this is basically the authorization header if we hit send here now we're getting back a 200 okay and it knows that that token was from Mumbo right so it validated that the user is logged in and also pulled up their user record so now they have access to this protected route so pretty much we're done we now have the ability to log in without passwords and also maintain a user session using jwts all right guys so that's pretty much it for me today hopefully you found this tutorial useful in some way uh if you got any feedback let me know in the comments do you think you'd use this in your application what thoughts you have anyways make sure to like the video and subscribe if you want to see more of these and hopefully I'll see you in the next video
Info
Channel: Marius Espejo
Views: 5,913
Rating: undefined out of 5
Keywords: nestjs auth, nest auth, nest authentication, nestjs authentication, jwt, jsonwebtokens, json web tokens, express jwt, express session, nestjs jwt, nest jwt, magic link auth, magic link, passwordless, passwordless auth, nestjs passwordless auth, nest passwordless auth, nest.js auth, nestjs magic link auth, password-less auth, passwordless authentication, magic link authentication, nestjs guards, passportjs, nestjs passport, express passportjs, express passport
Id: 69QmlAXNgHk
Channel Id: undefined
Length: 35min 55sec (2155 seconds)
Published: Wed Feb 22 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.