WEBVTT

00:00.050 --> 00:05.620
So we've cleaned things up a little bit and now we are actually correctly setting up the JWT module.

00:05.630 --> 00:10.250
This is all great and we're ready to finally start implementing our authentication.

00:10.250 --> 00:16.280
So in order to do this, like I said before, we're taking advantage of Passport, which is a common

00:16.280 --> 00:21.440
and popular authentication library that makes authentication super easy to set up.

00:21.440 --> 00:28.460
So if Passport has the idea of strategies in Nestjs where you can implement a new strategy for each

00:28.460 --> 00:34.820
kind of authentication we want to use, so we'll have a strategy for logging in with an email and password,

00:34.820 --> 00:38.780
and then we'll also have a strategy for authentication with a JWT.

00:39.230 --> 00:42.500
So let's keep things organized here in our auth folder.

00:42.500 --> 00:48.440
Let's create a new strategies folder and we can start off by creating our first strategy.

00:48.440 --> 00:55.940
This will be the local strategy and this is going to be the strategy that allows us to log in with a

00:55.940 --> 01:00.810
user's email and password, which essentially will start off the authentication flow.

01:00.810 --> 01:04.500
So this will be a normal injectable class here.

01:04.500 --> 01:08.100
And so we'll export the local strategy here.

01:08.100 --> 01:14.910
And this importantly is going to extend the passport strategy that we want to actually use here.

01:15.090 --> 01:30.450
And so make sure we import up top the passport strategy from nestjs slash passport and now we pass in

01:30.450 --> 01:33.480
the actual strategy we want to use in this case.

01:33.480 --> 01:38.160
So you can see here we have two different strategies out of the box from what we've already installed.

01:38.190 --> 01:40.830
This will be the passport local strategy.

01:40.860 --> 01:45.210
So this is what essentially takes care of most of the work here.

01:45.210 --> 01:50.940
We just provide the strategy we want to use and then we need to take care of some basic configuration

01:50.940 --> 01:51.720
in this class.

01:51.720 --> 01:58.890
Firstly, we're going to have a constructor here and we're going to need to get access to the user service

01:58.890 --> 02:01.200
and we'll see why in a second here.

02:01.200 --> 02:09.960
So let's go ahead and just get the user's service and we can go ahead and import the user's service

02:09.960 --> 02:10.950
from.

02:11.630 --> 02:14.870
The users directory and the user service here.

02:14.870 --> 02:19.550
So also in the constructor here, we're going to make a call to the super.

02:19.580 --> 02:25.190
The extended passport strategy, where we can pass in some configuration options.

02:25.190 --> 02:31.010
And the option that we want to actually configure here is the username field.

02:31.010 --> 02:36.710
And the username field is going to allow us to specify a different field to check the username on.

02:36.710 --> 02:43.640
And in our case we want to use the email field for the username because the user is passing us their

02:43.640 --> 02:44.870
email to log in.

02:44.870 --> 02:49.640
And now lastly, the only other thing we have to do in this strategy to make it actually work out of

02:49.640 --> 02:57.380
the box is implement an async validate method here, which is going to get called by our extended passport

02:57.380 --> 03:02.060
strategy when we actually want to check to see if this user is valid.

03:02.060 --> 03:09.650
So we're going to get past the email and password for the user that's trying to be authenticated here.

03:09.650 --> 03:13.830
And it's our job to check to see if this user is indeed a valid user.

03:13.830 --> 03:20.460
And to do this, we're actually going to hand this off to the user service to keep the responsibility

03:20.460 --> 03:22.500
really clean and clear here.

03:22.500 --> 03:29.130
So let's return a call to this user service dot, validate user where we will pass in the email and

03:29.130 --> 03:29.850
password.

03:29.850 --> 03:34.650
Then of course we'll actually have to go to the user service here and implement this logic.

03:34.650 --> 03:39.780
So in this user service we're going to actually make sure that the user is indeed valid.

03:39.810 --> 03:44.880
Now before we do that, we're going to go ahead and refactor our user creation a little bit here because

03:44.880 --> 03:51.480
right now all we're doing is accepting the user's password as plain text and persisting that to the

03:51.480 --> 03:55.230
database, which of course is not a good practice at all.

03:55.230 --> 03:58.440
We never want to store plain text passwords in the database.

03:58.440 --> 04:01.560
Instead, we actually want to hash those passwords.

04:01.710 --> 04:04.410
And this is super easy to do.

04:04.440 --> 04:07.230
We're just going to take advantage of a third party library.

04:07.230 --> 04:11.670
In this case, Bcrypt, a super popular hashing library.

04:11.670 --> 04:18.600
So let's go ahead and install Bcrypt here by running NPM install B crypt.

04:19.180 --> 04:22.510
And then we'll go ahead and start our containers back up.

04:22.540 --> 04:31.900
So in our user service now, let's go ahead and import star as Bcrypt from Bcrypt.

04:31.900 --> 04:36.360
And you can see it's complaining here because we didn't install the types as well.

04:36.370 --> 04:38.170
So let's quickly do that as well.

04:38.170 --> 04:45.070
We can install the development dependency type slash bcrypt to get a little bit of type information

04:45.700 --> 04:47.530
and then we'll start up again.

04:47.530 --> 04:54.400
So now in Create here, instead of just creating the straight DTO, I want to open an object here and

04:54.400 --> 04:59.260
spread the create user DTO so we'll add any of the additional properties like the email.

04:59.260 --> 05:04.030
Now, instead though, we're going to override the password here and instead of the straight password,

05:04.030 --> 05:11.050
we're going to call bcrypt dot hash and pass in the create user DTO password.

05:11.050 --> 05:16.690
And then we can provide the number of salt rounds when we hash this password, which is essentially

05:16.690 --> 05:19.390
just an additional layer of security.

05:19.390 --> 05:26.380
It's some random characters that get added on to the hashed password to help prevent it from being decrypted.

05:26.380 --> 05:30.370
So we'll add ten salt rounds here when we hash the password.

05:30.370 --> 05:37.180
So go ahead and create that async validate user method here that gets the email of type string and password

05:37.180 --> 05:38.710
of type string as well.

05:38.710 --> 05:42.760
And the first thing we're going to do here is make sure the user actually exists.

05:42.760 --> 05:46.870
So we'll call this DOT users repository dot, find one.

05:46.990 --> 05:50.890
We'll pass in the email here to make sure that the user exists.

05:50.890 --> 05:57.070
And if the user does exist, then we need to check to see if the password is indeed valid.

05:57.070 --> 05:59.530
So we can use bcrypt again here.

05:59.530 --> 06:08.290
We'll call bcrypt, await bcrypt compare and we pass in the password we are trying to use as well as

06:08.290 --> 06:10.960
the password that's actually on the user itself.

06:10.960 --> 06:14.800
So we'll check to see if the password is not valid.

06:14.830 --> 06:24.320
Then I want to throw a new unauthorized exception here and we'll say credentials are not valid.

06:24.320 --> 06:28.850
And finally, if this user is valid, we will return it back to the caller here.

06:28.850 --> 06:33.890
So now back in our local strategy, I'm actually going to wrap this in a try.

06:34.250 --> 06:35.930
Try catch block here.

06:35.930 --> 06:42.080
And the reason why we do this is so I'll put the return await validate user in here.

06:42.110 --> 06:48.140
The reason why I want to do this is because if the user is not actually found, then the user's repository

06:48.140 --> 06:51.890
of course will throw that 404 not found exception.

06:51.890 --> 06:58.130
So I want to trap that exception, if that's the case, and re throw it as an unauthorized exception

06:58.130 --> 07:00.920
here and we'll just pass in the error.

07:00.920 --> 07:06.860
And finally, I'm just going to change the name of this function to verify user instead, I think it's

07:06.860 --> 07:08.690
a bit more descriptive.

07:08.690 --> 07:15.410
So now that we have this all set up, let's go back into our auth dot controller here where we can finally

07:15.410 --> 07:20.860
implement a new post route here that's actually called login.

07:20.860 --> 07:24.610
So we're actually going to be able to log in with our username and password here.

07:24.610 --> 07:32.350
And importantly, we're going to add another decorator here called Use Guards from Nestjs Common, which

07:32.350 --> 07:35.350
will actually execute a given guard.

07:35.350 --> 07:41.650
So we want to pass in our strategy here to make sure that our strategy we just created runs before we

07:41.650 --> 07:43.990
actually execute this login route.

07:43.990 --> 07:48.490
So we want to make sure that the email and password the user provides here is run through our local

07:48.490 --> 07:49.420
strategy.

07:49.720 --> 07:51.790
And so that's really easy to do.

07:51.820 --> 08:00.310
We just need to create a new folder here called Guards and we'll go ahead and create a local auth guard

08:00.310 --> 08:01.180
in here.

08:01.180 --> 08:11.530
And all we have to do is export this local auth guard which will extend auth guard from Nestjs passport

08:11.530 --> 08:15.430
and then we just pass in the name of the strategy we're using.

08:15.430 --> 08:22.910
So in this case it's local and this string value here actually corresponds to the name of the strategy

08:22.910 --> 08:23.510
we're using.

08:23.510 --> 08:27.470
And so you can actually override this here as a second option.

08:27.470 --> 08:32.930
You can change the name of the strategy if you want, but by default, this will be local here.

08:32.930 --> 08:40.070
So with this all set up, we can actually use this new local auth guard in this route here now and we

08:40.070 --> 08:44.090
will add our async login method.

08:44.090 --> 08:52.430
And so now instead of this login method, I want to actually get access to the current user after they've

08:52.430 --> 08:54.440
run through the local auth guard.

08:54.440 --> 08:59.540
And we'll actually want to do this in potentially a lot of different places in our controller.

08:59.540 --> 09:04.880
It's a common pattern where you know, you want to get access to the current user in the route after

09:04.880 --> 09:06.200
you've authenticated them.

09:06.200 --> 09:13.310
And so to do this we'll actually create our own decorator here, which is actually really easy to do.

09:13.340 --> 09:17.780
We will just create a new current user.

09:18.350 --> 09:20.010
Creator thoughts.

09:20.030 --> 09:25.880
And inside of here, we'll go ahead and export a const here called current user, which is equal to

09:25.880 --> 09:33.800
create param decorator from Nestjs Common, where we pass in a function here where we get access to

09:33.800 --> 09:39.500
the data which will be of type unknown, which we don't actually need, and then the execution context

09:39.500 --> 09:40.270
here.

09:40.280 --> 09:44.360
So add the execution context and then we're going to return a call.

09:44.480 --> 09:51.320
In this case, it will be another function we'll create called get current user by context.

09:51.320 --> 09:53.210
And we will pass in that context.

09:53.210 --> 09:55.190
And this just keeps things a bit cleaner.

09:55.310 --> 09:58.790
So we'll go ahead and create that function up here.

09:58.820 --> 10:07.190
A const, get current user by context, which of course gets that execution context and we'll go ahead

10:07.190 --> 10:09.500
and return a user document.

10:09.500 --> 10:12.110
And you might be wondering how does this actually work?

10:12.110 --> 10:17.360
Why are we getting access to a user document in our auth controller here?

10:17.360 --> 10:23.490
And the reason for that is because in our local strategy, if we go back to the local strategy here

10:23.490 --> 10:29.670
in this validate call, when we verify the user here, we returned the user.

10:30.300 --> 10:37.080
So whatever gets returned from this local strategy in the validate method here gets automatically added

10:37.080 --> 10:41.160
to the request object as the user property.

10:41.160 --> 10:47.730
So essentially what I want to do in this decorator is pull the user off of the request and make it accessible

10:47.730 --> 10:48.840
in this controller.

10:48.840 --> 10:51.480
So it's important to understand how this is working here.

10:51.480 --> 10:59.040
So we'll return context dot switch to HTTP where we get actually the get request object.

10:59.040 --> 11:02.280
And now we have the request object.

11:02.430 --> 11:08.730
We can pull the user off of it, make sure we go ahead and import the user document from user models,

11:08.730 --> 11:10.080
user dot schema here.

11:10.080 --> 11:16.440
So now inside of here as a parameter, we can actually pull the current user off of the request object

11:16.440 --> 11:19.560
very easily by just calling this param decorator here.

11:19.590 --> 11:22.500
And now we have the user, which is great.

11:22.500 --> 11:28.230
And so the second parameter I'm going to add here is actually I want to get access to the response object

11:28.230 --> 11:33.000
itself here, as well as this property here called pass through.

11:33.000 --> 11:40.260
And the reason we're going to do this is we're going to actually set the JWT as a cookie on the response

11:40.260 --> 11:46.590
object instead of passing as plain text, because I think HTTP cookies are much more secure.

11:46.590 --> 11:48.390
So this is really easy to do.

11:48.420 --> 11:54.630
We just need to add the response decorator here and the pass through option is going to allow us to

11:54.630 --> 11:59.670
make sure that the response is sent manually within the route handler with the use of native response

11:59.670 --> 12:00.720
handling methods.

12:00.720 --> 12:03.000
So I'll go ahead and set this to true.

12:03.000 --> 12:07.350
And then we get access to the response object from Express here.

12:07.350 --> 12:13.590
So now we're going to go ahead and call await this dot auth service dot login, which we don't currently

12:13.590 --> 12:18.270
have set up, but I'm going to pass the user in the response to it.

12:18.270 --> 12:24.210
And after we log the user in, then I'm going to respond on the response object by calling response,

12:24.210 --> 12:26.910
dot send and passing in the user.

12:26.910 --> 12:32.820
So let's go ahead and implement the login method in the auth service, which is the most important part

12:32.820 --> 12:33.600
of all of this.

12:33.600 --> 12:37.170
So it's important to remember at this point we know that the user is actually valid.

12:37.200 --> 12:38.940
They've been through the local off guard.

12:38.940 --> 12:45.810
We've set the user on the request object itself and we're ready to set the JWT for this user.

12:45.810 --> 12:52.560
So in the auth service we'll implement this async login method where we get the user that we know is

12:52.560 --> 12:57.330
validated and the response object here from Express.

12:57.330 --> 13:01.320
So there's two dependencies we'll need in the auth service here.

13:01.350 --> 13:08.430
We can get rid of this get hello function, also remove it from the auth controller here.

13:08.430 --> 13:11.700
So in the auth service here, we're going to need two dependencies.

13:11.700 --> 13:18.600
The first will be the config service to make sure we get those JWT environment variables that we used

13:18.600 --> 13:19.410
earlier.

13:19.740 --> 13:27.840
And then we're going to get access to the JWT service which comes from Nestjs, JWT.

13:28.170 --> 13:33.210
So now that we have those dependencies in our login method, the first thing we're going to do here

13:33.210 --> 13:40.800
is create a token payload, which is just going to be the information that's stored on the encrypted

13:40.830 --> 13:41.550
token.

13:41.550 --> 13:48.960
In this case, all I'm going to add is the user ID here by calling user ID dot two hex string.

13:48.960 --> 13:55.020
So I'm just going to get the user ID on the token itself and then I'm going to set the expiration date

13:55.020 --> 13:55.830
for the token.

13:55.830 --> 14:02.700
So to do this I'll create a new variable here called expires that will start right now and then I will

14:02.700 --> 14:08.460
call expires dot set seconds here so we can set the correct expiration.

14:08.460 --> 14:14.490
And to do this I'll call expires again dot get seconds to get the current seconds on the expiration

14:14.490 --> 14:18.270
date and then I'm going to add the number of seconds.

14:18.370 --> 14:20.760
Our JWT expiration is set to.

14:20.790 --> 14:28.320
So to do this, we can call this config service dot get and then we'll get that JWT expiration here.

14:28.320 --> 14:34.410
And so now we have an expiration date that expires at the correct time after our expiration.

14:34.410 --> 14:40.170
And so the last step here is to finally generate the token itself, which we can use by calling this

14:40.350 --> 14:42.750
JWT service dot sign.

14:42.750 --> 14:46.650
And then we pass in the token payload here that we created.

14:46.650 --> 14:48.480
So now we have everything set up.

14:48.480 --> 14:53.370
We're ready to finally set the cookie on the response here.

14:53.370 --> 14:59.310
So I'll go ahead and set the authentication cookie on the response object.

14:59.340 --> 15:03.510
I'll pass the sign token payload as the actual property.

15:03.510 --> 15:09.780
And then we have an options object here where I will set the HTTP only option here, which makes sure

15:09.780 --> 15:16.170
that this cookie is only available for HTTP requests itself, making it even more secure so that people

15:16.170 --> 15:21.880
can't actually deal with this cookie on the client side without sending requests.

15:21.880 --> 15:28.000
And finally, we'll set the expiration for the cookie to match the expiration of the JWT itself.

15:28.390 --> 15:30.580
So we go ahead and set the cookie here.

15:30.580 --> 15:36.280
And then finally in the auth controller, we send the response back on the response object ending this

15:36.280 --> 15:37.030
flow.

15:37.030 --> 15:42.070
So in our auth module, the only other thing we need to do here, make sure we add the local strategy

15:42.070 --> 15:46.690
we created here as a provider because it is an injectable class.

15:47.110 --> 15:47.470
Okay.

15:47.470 --> 15:51.820
So there's two things we're going to take care of before we start up our containers again.

15:51.820 --> 15:54.790
We're going to actually install an alternative to Bcrypt.

15:54.790 --> 16:02.020
It's going to be Bcrypt JS as well as make sure we actually have Express installed here and we're going

16:02.020 --> 16:09.550
to use Bcrypt JS to avoid an issue with mounting our volumes in Docker and expresses to make sure we

16:09.550 --> 16:12.250
get the response types we need here.

16:12.250 --> 16:20.120
So once we have these installed, go back to our user service and let's make sure we import Bcrypt JS

16:20.140 --> 16:24.760
here and we can also make sure we add the types here as well for Bcrypt.

16:24.760 --> 16:29.650
JS So I'll add the types here and install this guy.

16:29.650 --> 16:37.060
So make sure we import from Bcrypt JS and then we can go ahead in our Package.json just remove Bcrypt

16:37.060 --> 16:39.400
here as well as the types.

16:39.400 --> 16:45.550
And once we have this all set up, we can go ahead and run Docker, compose up again to start our containers.

16:45.550 --> 16:52.390
So once our app starts up, we can see we're getting an error here saying that the user service is not

16:52.390 --> 16:54.610
available in the auth module.

16:54.610 --> 17:00.430
So if we go ahead and take a look, we can see that we are actually injecting the user service in the

17:00.430 --> 17:01.870
local strategy here.

17:01.870 --> 17:08.620
So we need to make sure in the users module here that we actually add the users service to our exports

17:08.650 --> 17:13.840
array so that it can be injected in other services like the local strategy.

17:13.840 --> 17:17.080
So now our auth service has successfully started up again.

17:17.080 --> 17:23.710
I'm going to go ahead and open up Postman and launch a post request to localhost 3001 slash users to

17:23.710 --> 17:27.490
create a new user here with a hashed password this time.

17:27.490 --> 17:33.400
So I still have an email and password set up and I'll send this request to create my new user.

17:33.400 --> 17:39.760
And now you can see we are getting the hashed password back, which is much better to see.

17:39.760 --> 17:47.950
And now we can test out our login functionality by calling localhost 3001 slash auth and then we'll

17:47.950 --> 17:51.550
call slash log in here and it's going to be the same payload.

17:51.550 --> 17:53.950
Of course we're going to have the email and password.

17:53.950 --> 17:59.380
I'll send this request off and we are getting a 404 can't find our route.

17:59.740 --> 18:05.710
So let's go back to our auth controller and of course make sure we actually add the auth prefix to this

18:05.710 --> 18:06.370
controller.

18:06.370 --> 18:12.670
So now if we launch a request to log in, we get the user response back, which is good, make sure

18:12.670 --> 18:14.800
that we actually did succeed.

18:14.800 --> 18:20.830
However, now if we go to the cookies section for this request, we can see we get that all important

18:20.970 --> 18:26.310
authentication cookie, which is great because now we can use this JWT to authenticate ourselves to

18:26.310 --> 18:30.960
all of our different microservices and make sure that we are indeed authenticated.

18:30.960 --> 18:37.080
So to make sure this works properly, I'll just quickly change the password here to something invalid.

18:37.080 --> 18:39.390
And we can see now we get a 401.

18:39.420 --> 18:40.650
Unauthorized.

18:40.680 --> 18:47.220
Or on the other hand, if I change the email here to a non-existent email, we get that document not

18:47.220 --> 18:47.910
found.

18:47.910 --> 18:48.240
Okay.

18:48.240 --> 18:53.430
So we can log in with a username and password in our system and get back that JWT.
