So we've cleaned things up a little bit and now we are actually correctly setting up the JWT module.
This is all great and we're ready to finally start implementing our authentication.
So in order to do this, like I said before, we're taking advantage of Passport, which is a common
and popular authentication library that makes authentication super easy to set up.
So if Passport has the idea of strategies in Nestjs where you can implement a new strategy for each
kind of authentication we want to use, so we'll have a strategy for logging in with an email and password,
and then we'll also have a strategy for authentication with a JWT.
So let's keep things organized here in our auth folder.
Let's create a new strategies folder and we can start off by creating our first strategy.
This will be the local strategy and this is going to be the strategy that allows us to log in with a
user's email and password, which essentially will start off the authentication flow.
So this will be a normal injectable class here.
And so we'll export the local strategy here.
And this importantly is going to extend the passport strategy that we want to actually use here.
And so make sure we import up top the passport strategy from nestjs slash passport and now we pass in
the actual strategy we want to use in this case.
So you can see here we have two different strategies out of the box from what we've already installed.
This will be the passport local strategy.
So this is what essentially takes care of most of the work here.
We just provide the strategy we want to use and then we need to take care of some basic configuration
in this class.
Firstly, we're going to have a constructor here and we're going to need to get access to the user service
and we'll see why in a second here.
So let's go ahead and just get the user's service and we can go ahead and import the user's service
from.
The users directory and the user service here.
So also in the constructor here, we're going to make a call to the super.
The extended passport strategy, where we can pass in some configuration options.
And the option that we want to actually configure here is the username field.
And the username field is going to allow us to specify a different field to check the username on.
And in our case we want to use the email field for the username because the user is passing us their
email to log in.
And now lastly, the only other thing we have to do in this strategy to make it actually work out of
the box is implement an async validate method here, which is going to get called by our extended passport
strategy when we actually want to check to see if this user is valid.
So we're going to get past the email and password for the user that's trying to be authenticated here.
And it's our job to check to see if this user is indeed a valid user.
And to do this, we're actually going to hand this off to the user service to keep the responsibility
really clean and clear here.
So let's return a call to this user service dot, validate user where we will pass in the email and
password.
Then of course we'll actually have to go to the user service here and implement this logic.
So in this user service we're going to actually make sure that the user is indeed valid.
Now before we do that, we're going to go ahead and refactor our user creation a little bit here because
right now all we're doing is accepting the user's password as plain text and persisting that to the
database, which of course is not a good practice at all.
We never want to store plain text passwords in the database.
Instead, we actually want to hash those passwords.
And this is super easy to do.
We're just going to take advantage of a third party library.
In this case, Bcrypt, a super popular hashing library.
So let's go ahead and install Bcrypt here by running NPM install B crypt.
And then we'll go ahead and start our containers back up.
So in our user service now, let's go ahead and import star as Bcrypt from Bcrypt.
And you can see it's complaining here because we didn't install the types as well.
So let's quickly do that as well.
We can install the development dependency type slash bcrypt to get a little bit of type information
and then we'll start up again.
So now in Create here, instead of just creating the straight DTO, I want to open an object here and
spread the create user DTO so we'll add any of the additional properties like the email.
Now, instead though, we're going to override the password here and instead of the straight password,
we're going to call bcrypt dot hash and pass in the create user DTO password.
And then we can provide the number of salt rounds when we hash this password, which is essentially
just an additional layer of security.
It's some random characters that get added on to the hashed password to help prevent it from being decrypted.
So we'll add ten salt rounds here when we hash the password.
So go ahead and create that async validate user method here that gets the email of type string and password
of type string as well.
And the first thing we're going to do here is make sure the user actually exists.
So we'll call this DOT users repository dot, find one.
We'll pass in the email here to make sure that the user exists.
And if the user does exist, then we need to check to see if the password is indeed valid.
So we can use bcrypt again here.
We'll call bcrypt, await bcrypt compare and we pass in the password we are trying to use as well as
the password that's actually on the user itself.
So we'll check to see if the password is not valid.
Then I want to throw a new unauthorized exception here and we'll say credentials are not valid.
And finally, if this user is valid, we will return it back to the caller here.
So now back in our local strategy, I'm actually going to wrap this in a try.
Try catch block here.
And the reason why we do this is so I'll put the return await validate user in here.
The reason why I want to do this is because if the user is not actually found, then the user's repository
of course will throw that 404 not found exception.
So I want to trap that exception, if that's the case, and re throw it as an unauthorized exception
here and we'll just pass in the error.
And finally, I'm just going to change the name of this function to verify user instead, I think it's
a bit more descriptive.
So now that we have this all set up, let's go back into our auth dot controller here where we can finally
implement a new post route here that's actually called login.
So we're actually going to be able to log in with our username and password here.
And importantly, we're going to add another decorator here called Use Guards from Nestjs Common, which
will actually execute a given guard.
So we want to pass in our strategy here to make sure that our strategy we just created runs before we
actually execute this login route.
So we want to make sure that the email and password the user provides here is run through our local
strategy.
And so that's really easy to do.
We just need to create a new folder here called Guards and we'll go ahead and create a local auth guard
in here.
And all we have to do is export this local auth guard which will extend auth guard from Nestjs passport
and then we just pass in the name of the strategy we're using.
So in this case it's local and this string value here actually corresponds to the name of the strategy
we're using.
And so you can actually override this here as a second option.
You can change the name of the strategy if you want, but by default, this will be local here.
So with this all set up, we can actually use this new local auth guard in this route here now and we
will add our async login method.
And so now instead of this login method, I want to actually get access to the current user after they've
run through the local auth guard.
And we'll actually want to do this in potentially a lot of different places in our controller.
It's a common pattern where you know, you want to get access to the current user in the route after
you've authenticated them.
And so to do this we'll actually create our own decorator here, which is actually really easy to do.
We will just create a new current user.
Creator thoughts.
And inside of here, we'll go ahead and export a const here called current user, which is equal to
create param decorator from Nestjs Common, where we pass in a function here where we get access to
the data which will be of type unknown, which we don't actually need, and then the execution context
here.
So add the execution context and then we're going to return a call.
In this case, it will be another function we'll create called get current user by context.
And we will pass in that context.
And this just keeps things a bit cleaner.
So we'll go ahead and create that function up here.
A const, get current user by context, which of course gets that execution context and we'll go ahead
and return a user document.
And you might be wondering how does this actually work?
Why are we getting access to a user document in our auth controller here?
And the reason for that is because in our local strategy, if we go back to the local strategy here
in this validate call, when we verify the user here, we returned the user.
So whatever gets returned from this local strategy in the validate method here gets automatically added
to the request object as the user property.
So essentially what I want to do in this decorator is pull the user off of the request and make it accessible
in this controller.
So it's important to understand how this is working here.
So we'll return context dot switch to HTTP where we get actually the get request object.
And now we have the request object.
We can pull the user off of it, make sure we go ahead and import the user document from user models,
user dot schema here.
So now inside of here as a parameter, we can actually pull the current user off of the request object
very easily by just calling this param decorator here.
And now we have the user, which is great.
And so the second parameter I'm going to add here is actually I want to get access to the response object
itself here, as well as this property here called pass through.
And the reason we're going to do this is we're going to actually set the JWT as a cookie on the response
object instead of passing as plain text, because I think HTTP cookies are much more secure.
So this is really easy to do.
We just need to add the response decorator here and the pass through option is going to allow us to
make sure that the response is sent manually within the route handler with the use of native response
handling methods.
So I'll go ahead and set this to true.
And then we get access to the response object from Express here.
So now we're going to go ahead and call await this dot auth service dot login, which we don't currently
have set up, but I'm going to pass the user in the response to it.
And after we log the user in, then I'm going to respond on the response object by calling response,
dot send and passing in the user.
So let's go ahead and implement the login method in the auth service, which is the most important part
of all of this.
So it's important to remember at this point we know that the user is actually valid.
They've been through the local off guard.
We've set the user on the request object itself and we're ready to set the JWT for this user.
So in the auth service we'll implement this async login method where we get the user that we know is
validated and the response object here from Express.
So there's two dependencies we'll need in the auth service here.
We can get rid of this get hello function, also remove it from the auth controller here.
So in the auth service here, we're going to need two dependencies.
The first will be the config service to make sure we get those JWT environment variables that we used
earlier.
And then we're going to get access to the JWT service which comes from Nestjs, JWT.
So now that we have those dependencies in our login method, the first thing we're going to do here
is create a token payload, which is just going to be the information that's stored on the encrypted
token.
In this case, all I'm going to add is the user ID here by calling user ID dot two hex string.
So I'm just going to get the user ID on the token itself and then I'm going to set the expiration date
for the token.
So to do this I'll create a new variable here called expires that will start right now and then I will
call expires dot set seconds here so we can set the correct expiration.
And to do this I'll call expires again dot get seconds to get the current seconds on the expiration
date and then I'm going to add the number of seconds.
Our JWT expiration is set to.
So to do this, we can call this config service dot get and then we'll get that JWT expiration here.
And so now we have an expiration date that expires at the correct time after our expiration.
And so the last step here is to finally generate the token itself, which we can use by calling this
JWT service dot sign.
And then we pass in the token payload here that we created.
So now we have everything set up.
We're ready to finally set the cookie on the response here.
So I'll go ahead and set the authentication cookie on the response object.
I'll pass the sign token payload as the actual property.
And then we have an options object here where I will set the HTTP only option here, which makes sure
that this cookie is only available for HTTP requests itself, making it even more secure so that people
can't actually deal with this cookie on the client side without sending requests.
And finally, we'll set the expiration for the cookie to match the expiration of the JWT itself.
So we go ahead and set the cookie here.
And then finally in the auth controller, we send the response back on the response object ending this
flow.
So in our auth module, the only other thing we need to do here, make sure we add the local strategy
we created here as a provider because it is an injectable class.
Okay.
So there's two things we're going to take care of before we start up our containers again.
We're going to actually install an alternative to Bcrypt.
It's going to be Bcrypt JS as well as make sure we actually have Express installed here and we're going
to use Bcrypt JS to avoid an issue with mounting our volumes in Docker and expresses to make sure we
get the response types we need here.
So once we have these installed, go back to our user service and let's make sure we import Bcrypt JS
here and we can also make sure we add the types here as well for Bcrypt.
JS So I'll add the types here and install this guy.
So make sure we import from Bcrypt JS and then we can go ahead in our Package.json just remove Bcrypt
here as well as the types.
And once we have this all set up, we can go ahead and run Docker, compose up again to start our containers.
So once our app starts up, we can see we're getting an error here saying that the user service is not
available in the auth module.
So if we go ahead and take a look, we can see that we are actually injecting the user service in the
local strategy here.
So we need to make sure in the users module here that we actually add the users service to our exports
array so that it can be injected in other services like the local strategy.
So now our auth service has successfully started up again.
I'm going to go ahead and open up Postman and launch a post request to localhost 3001 slash users to
create a new user here with a hashed password this time.
So I still have an email and password set up and I'll send this request to create my new user.
And now you can see we are getting the hashed password back, which is much better to see.
And now we can test out our login functionality by calling localhost 3001 slash auth and then we'll
call slash log in here and it's going to be the same payload.
Of course we're going to have the email and password.
I'll send this request off and we are getting a 404 can't find our route.
So let's go back to our auth controller and of course make sure we actually add the auth prefix to this
controller.
So now if we launch a request to log in, we get the user response back, which is good, make sure
that we actually did succeed.
However, now if we go to the cookies section for this request, we can see we get that all important
authentication cookie, which is great because now we can use this JWT to authenticate ourselves to
all of our different microservices and make sure that we are indeed authenticated.
So to make sure this works properly, I'll just quickly change the password here to something invalid.
And we can see now we get a 401.
Unauthorized.
Or on the other hand, if I change the email here to a non-existent email, we get that document not
found.
Okay.
So we can log in with a username and password in our system and get back that JWT.