WEBVTT

00:00.200 --> 00:00.440
Okay.

00:00.470 --> 00:06.530
So I want to show you how we can easily add role functionality to our authentication and users right

00:06.530 --> 00:12.230
now so that we can provide different roles to our users and ensure that only users with those roles

00:12.230 --> 00:14.510
can access certain routes in our system.

00:14.510 --> 00:23.450
So the first step in this will be going into auth and going to the user DTO in the create user, DTO,

00:23.480 --> 00:29.540
we're now going to accept in addition to an email and password, we're also going to accept an array

00:29.540 --> 00:32.420
of roles that a user can pass in.

00:32.420 --> 00:38.060
So this will be an optional parameter which will just be a array of strings and we'll provide an is

00:38.090 --> 00:45.470
optional decorator from class validator to mark this as optional so that if it's not provided it won't

00:45.470 --> 00:47.480
apply the rest of our validators.

00:47.510 --> 00:54.170
Otherwise we're going to make sure this is an array and then we'll call the IS string validator to make

00:54.170 --> 00:58.670
sure that each value is a string and to make sure each value is validated.

00:58.670 --> 01:03.630
Here we will call each which specifies of the validated value is an array.

01:03.660 --> 01:07.080
Each of the items must be validated, which is what we want.

01:07.080 --> 01:09.060
So each item will be a string.

01:09.060 --> 01:16.380
And importantly we can also say is not empty for each value to make sure that the empty string is not

01:16.380 --> 01:16.920
provided.

01:16.920 --> 01:24.690
So in addition to our DTO, let's go ahead and copy this property, the roles property and add it to

01:24.690 --> 01:27.450
our user model which we know lives in libs.

01:27.450 --> 01:31.860
Common Source models User dot Schema.

01:31.890 --> 01:39.180
Let's go ahead and add this roles property to the actual schema here by calling at prop.

01:39.270 --> 01:45.240
Finally, we'll update the common user DTO to add the new roles array.

01:45.420 --> 01:47.820
So I have my containers running in the background.

01:47.820 --> 01:54.450
I ran Docker compose up and now let's try creating a new user with this roles array.

01:55.080 --> 02:00.990
Firstly, I'll just show that we can still create a new dummy user without roles.

02:00.990 --> 02:06.150
That works fine and you can see by default the roles will be empty for that user.

02:06.150 --> 02:10.590
However, we want to go ahead and supply some custom roles to make sure that they get saved.

02:10.590 --> 02:15.870
So let's provide a new Roles property, which is an array, and I'll have a new admin role that gets

02:15.870 --> 02:17.190
created along with this user.

02:17.190 --> 02:20.310
And you can see we get that user back here.

02:20.310 --> 02:27.930
We can also then try to get this user by going to the auth slash login route with the user credentials,

02:27.930 --> 02:33.090
with the roles, user, log in with this guy and then come back and try to get users.

02:33.090 --> 02:35.580
We can see we have our roles array saved.

02:35.580 --> 02:42.930
So now to actually implement our roles functionality, let's go into Libs common source decorators and

02:42.930 --> 02:44.670
we're going to add a new decorator.

02:44.670 --> 02:52.920
We'll call this roles dot decorator dot TS, and we're going to use Nestjs functionality to easily create

02:52.920 --> 02:55.140
a new class or route decorator.

02:55.140 --> 03:02.220
So to do this we'll export a new const called Roles set it equal to a function.

03:02.220 --> 03:08.430
And so this function is going to take n number of roles, which will just be an array of string that

03:08.430 --> 03:09.540
will spread here.

03:09.540 --> 03:15.690
And then we're going to call set metadata from Nestjs Common that allows us to assign metadata to a

03:15.690 --> 03:18.150
class or function using a certain key.

03:18.150 --> 03:22.350
And you can see the exact example we're using here is what they're doing.

03:22.350 --> 03:29.580
So we'll set a metadata called roles and then pass in the the array of role strings we have.

03:29.580 --> 03:36.990
And then finally, don't forget to export our new decorator by calling export everything from roles

03:36.990 --> 03:38.220
dot decorator.

03:38.310 --> 03:41.070
Now let's actually see this decorator in action.

03:41.070 --> 03:44.190
We'll go directly to our reservations dot controller.

03:44.190 --> 03:51.690
And let's say that we only want to allow reservations to be deleted if we have an admin role.

03:51.690 --> 03:59.490
So we can now call add roles, import this from app slash common and now we can enter in any number

03:59.490 --> 04:07.140
of string roles that this route needs to have so we can have admin owner or any number of custom roles.

04:07.140 --> 04:10.350
I'm going to go ahead and just specify the admin role.

04:10.350 --> 04:17.760
So I want to go ahead and actually use the admin roles decorator now and actually verify that the user

04:17.760 --> 04:18.750
does have this role.

04:18.750 --> 04:24.810
And the perfect place to do that is in our JWT auth guard, the common common auth guard that all of

04:24.810 --> 04:27.390
our protected routes are already using.

04:27.390 --> 04:31.680
We know we're getting back the user from our auth call.

04:31.680 --> 04:36.300
So when we have this user we can check the roles on the user and make sure that they match.

04:36.300 --> 04:45.090
So in our auth guard we can get access to the decorator and its metadata by using the reflector from

04:45.090 --> 04:46.860
nestjs slash core.

04:46.890 --> 04:49.140
This is a new dependency we'll inject.

04:49.140 --> 04:56.940
So injecting new dependency called private read only reflector of type reflector from nestjs core.

04:56.940 --> 04:59.910
Now to get the roles assigned with this route.

05:00.080 --> 05:05.510
In the can activate method in addition to getting and checking the JWT.

05:05.870 --> 05:15.350
I'm also going to go ahead and get the roles by calling this dot reflector dot get and getting.

05:16.250 --> 05:23.780
The role's key, which we set earlier, and then providing context, which is the execution context

05:23.780 --> 05:26.970
passed into, can activate, already get handler.

05:26.990 --> 05:32.180
We can also provide some type information to make sure this knows we're getting back an array of strings.

05:32.210 --> 05:35.870
Now we have the roles assigned to the route handler.

05:35.870 --> 05:39.500
In this case, it will be the controller route that we are calling.

05:39.500 --> 05:45.830
So now, before setting the user on the request, we can check to see if our roles are valid on this

05:45.830 --> 05:48.150
user given the current route we're on.

05:48.170 --> 05:56.290
To do this, I'll loop through a new for loop on our route roles by calling for const role of roles.

05:56.310 --> 06:01.250
So now I'm going to check to see if not rez dot roles.

06:01.840 --> 06:09.070
Dot includes roll, then I'm going to throw a new unauthorized.

06:09.430 --> 06:12.790
Then I want to throw a new unauthorized exception.

06:12.790 --> 06:18.010
And I also want to log out an error so that we can actually know that the user didn't have valid roles.

06:18.010 --> 06:27.580
So to do that, I'll declare a new private read only logger set equal to new logger from Nestjs common

06:27.580 --> 06:31.750
and provide the JDBC auth guard.name for some context.

06:31.750 --> 06:40.770
And then we can call this dot logger dot error where we specify the user does not have valid roles.

06:40.780 --> 06:46.750
Additionally, let's add a new if check to make sure that roles exists before we actually loop through

06:46.750 --> 06:48.460
the roles to do this check.

06:48.460 --> 06:55.660
And I also want to add to our catch error block here to actually get the error in question and log it

06:55.660 --> 07:01.240
out just to make debugging a little bit easier in case we never know why we are getting unauthorized

07:01.240 --> 07:06.320
exceptions is so make sure we still return the error here as an observable as well.

07:06.320 --> 07:08.000
So now we can test this out.

07:08.000 --> 07:12.260
I'm going to log in as the user I created with the admin role here.

07:12.260 --> 07:18.770
So we get the JWT cookie set and then I want to go ahead and get all the reservations in the system

07:18.770 --> 07:21.440
so we can try deleting one with our admin role.

07:21.440 --> 07:29.810
So I'll take the ID off of this reservation, pass it as a root parameter and then call delete to delete

07:29.810 --> 07:30.560
the root.

07:30.650 --> 07:32.210
You can see we have a 200.

07:32.210 --> 07:33.500
Okay status code.

07:33.500 --> 07:38.720
And now if we get all reservations again we should see that we have only one now.

07:38.720 --> 07:43.490
So let's go ahead and try the same thing out with a new user that now doesn't have the admin role.

07:43.490 --> 07:49.220
So I'll create a new user with no roles and send a request to create this user.

07:49.580 --> 07:54.860
Make sure we send a post route to create the user so you can see we have no roles.

07:54.860 --> 08:03.230
Now let's copy this payload to the log in route so we get the JDBC cookie set on that guy log in and

08:03.230 --> 08:10.880
now let's go ahead and get all the reservations and we'll try deleting this one with the delete route.

08:10.880 --> 08:14.600
And if we send this off, we can see we have a 403 forbidden.

08:14.600 --> 08:21.350
And if we look in the logs, we can actually see that we have an unauthorized exception being thrown.

08:21.350 --> 08:27.050
And if we scroll up a bit, we can see the error in the reservation service that we logged out saying

08:27.050 --> 08:32.960
the user does not have valid roles with all of the requests contexts after it.

08:32.960 --> 08:34.340
So this is exactly what we want.

08:34.370 --> 08:37.130
We have basic role functionality now implemented.
