Guards
Guards
A guard is a class annotated with the @Injectable() decorator, which implements the
CanActivate interface.
Guards have a single responsibility. They determine whether a given request will be
handled by the route handler or not, depending on certain conditions (like
permissions, roles, ACLs, etc.) present at run-time. This is often referred to as
authorization. Authorization (and its cousin, authentication, with which it usually
collaborates) has typically been handled by middleware in traditional Express
applications. Middleware is a fine choice for authentication, since things like
token validation and attaching properties to the request object are not strongly
connected with a particular route context (and its metadata).
But middleware, by its nature, is dumb. It doesn't know which handler will be
executed after calling the next() function. On the other hand, Guards have access
to the ExecutionContext instance, and thus know exactly what's going to be executed
next. They're designed, much like exception filters, pipes, and interceptors, to
let you interpose processing logic at exactly the right point in the
request/response cycle, and to do so declaratively. This helps keep your code DRY
and declarative.
Hint
Guards are executed after all middleware, but before any interceptor or pipe.
Authorization guard#
As mentioned, authorization is a great use case for Guards because specific routes
should be available only when the caller (usually a specific authenticated user)
has sufficient permissions. The AuthGuard that we'll build now assumes an
authenticated user (and that, therefore, a token is attached to the request
headers). It will extract and validate the token, and use the extracted information
to determine whether the request can proceed or not.
auth.guard.ts
JS
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return validateRequest(request);
}
}
Hint
If you are looking for a real-world example on how to implement an
authentication mechanism in your application, visit this chapter. Likewise, for
more sophisticated authorization example, check this page.
Execution context#
Let's build a more functional guard that permits access only to users with a
specific role. We'll start with a basic guard template, and build on it in the
coming sections. For now, it allows all requests to proceed:
roles.guard.ts
JS
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}
Binding guards#
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
Hint
The @UseGuards() decorator is imported from the @nestjs/common package.
Above, we passed the RolesGuard class (instead of an instance), leaving
responsibility for instantiation to the framework and enabling dependency
injection. As with pipes and exception filters, we can also pass an in-place
instance:
JS
@Controller('cats')
@UseGuards(new RolesGuard())
export class CatsController {}
The construction above attaches the guard to every handler declared by this
controller. If we wish the guard to apply only to a single method, we apply the
@UseGuards() decorator at the method level.
In order to set up a global guard, use the useGlobalGuards() method of the Nest
application instance:
JS
Notice
In the case of hybrid apps the useGlobalGuards() method doesn't set up guards
for gateways and microservices by default (see Hybrid application for information
on how to change this behavior). For "standard" (non-hybrid) microservice apps,
useGlobalGuards() does mount the guards globally.
Global guards are used across the whole application, for every controller and every
route handler. In terms of dependency injection, global guards registered from
outside of any module (with useGlobalGuards() as in the example above) cannot
inject dependencies since this is done outside the context of any module. In order
to solve this issue, you can set up a guard directly from any module using the
following construction:
app.module.ts
JS
@Module({
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class AppModule {}
Hint
When using this approach to perform dependency injection for the guard, note
that regardless of the module where this construction is employed, the guard is, in
fact, global. Where should this be done? Choose the module where the guard
(RolesGuard in the example above) is defined. Also, useClass is not the only way of
dealing with custom provider registration. Learn more here.
Setting roles per handler#
Our RolesGuard is working, but it's not very smart yet. We're not yet taking
advantage of the most important guard feature - the execution context. It doesn't
yet know about roles, or which roles are allowed for each handler. The
CatsController, for example, could have different permission schemes for different
routes. Some might be available only for an admin user, and others could be open
for everyone. How can we match roles to routes in a flexible and reusable way?
This is where custom metadata comes into play (learn more here). Nest provides the
ability to attach custom metadata to route handlers through either decorators
created via Reflector.createDecorator static method, or the built-in @SetMetadata()
decorator.
The Roles decorator here is a function that takes a single argument of type
string[].
Now, to use this decorator, we simply annotate the handler with it:
cats.controller.ts
JS
@Post()
@Roles(['admin'])
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
Here we've attached the Roles decorator metadata to the create() method, indicating
that only users with the admin role should be allowed to access this route.
Let's now go back and tie this together with our RolesGuard. Currently, it simply
returns true in all cases, allowing every request to proceed. We want to make the
return value conditional based on comparing the roles assigned to the current user
to the actual roles required by the current route being processed. In order to
access the route's role(s) (custom metadata), we'll use the Reflector helper class
again, as follows:
roles.guard.ts
JS
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
Hint
In the node.js world, it's common practice to attach the authorized user to the
request object. Thus, in our sample code above, we are assuming that request.user
contains the user instance and allowed roles. In your app, you will probably make
that association in your custom authentication guard (or middleware). Check this
chapter for more information on this topic.
Warning
The logic inside the matchRoles() function can be as simple or sophisticated as
needed. The main point of this example is to show how guards fit into the
request/response cycle.
Refer to the Reflection and metadata section of the Execution context chapter for
more details on utilizing Reflector in a context-sensitive way.
{
"statusCode": 403,
"message": "Forbidden resource",
"error": "Forbidden"
}
Note that behind the scenes, when a guard returns false, the framework throws a
ForbiddenException. If you want to return a different error response, you should
throw your own specific exception. For example:
Any exception thrown by a guard will be handled by the exceptions layer (global
exceptions filter and any exceptions filters that are applied to the current
context).
Hint
If you are looking for a real-world example on how to implement authorization,
check this chapter.