Add a guard to the project (scaffold command)

nest g gu auth
Copy the code

Guard source code analysis

With the command above, we create a guard object:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable(a)export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return true; }}Copy the code

The @Injectable() decorator has two key points: its dependencies and dependencies are hosted by Nest; CanActivate interface, corresponding to find the corresponding source code, as follows:

export interface CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean>;
}
Copy the code

ExecutionContext is the context of the current request. This object has previously been used with a similar interface in filters (ArgumentsHost). See the Filters section for more details. The ExecutionContext interface extends the ArgumentsHost interface:

export interface ExecutionContext extends ArgumentsHost {
  getClass<T = any>(): Type<T>;
  getHandler(): Function;
}
Copy the code

The ExecutionContext interface is used for the guard’s canActiviate() method and the intercept() method of the interceptor (below). Compared to the ArgumentsHost interface, two methods are extended:

  • getClass(), class (controller) type, such as getClass().name returns AppController
  • getHandler() , method type, for example, getHandler().name returns getHello

This information is written to the object’s metadata via @setmetadata () after the decorator is used.

Binding the guards

This process is similar to exception filters, using the decorator UseGuards() to assign a guard to a set of routes for a class or method decorator. You can also use guards globally by registering them in the Module. Take a look at the binding guard decorator source code:

export function UseGuards(. guards: (CanActivate |Function) []) :MethodDecorator & ClassDecorator {
  return (
    target: any, key? :string| symbol, descriptor? : TypedPropertyDescriptor<any>,
  ) = > {
    const isGuardValid = <T extends Function | Record<string, any>>(guard: T) =>
      guard &&
      (isFunction(guard) ||
        isFunction((guard as Record<string, any>).canActivate));

    if (descriptor) {
      validateEach(
        target.constructor,
        guards,
        isGuardValid,
        '@UseGuards',
        'guard',
      );
      extendArrayMetadata(GUARDS_METADATA, guards, descriptor.value);
      return descriptor;
    }
    validateEach(target, guards, isGuardValid, '@UseGuards', 'guard');
    extendArrayMetadata(GUARDS_METADATA, guards, target);
    return target;
  };
}
Copy the code

The source code logic of the decorator is similar to that of the exception filter, which is validated and added to the class data as source data. Continuing with the guard code, let’s add a little more guard business process:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable(a)export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    switch (context.getType()) {
      case 'http':
        const [request, response, next] = context.getArgs();
        console.log(
          `${request.protocol}: / /${request.hostname}${ request.path }The ${JSON.stringify(request.query)}= >${context.getClass().name}.${ context.getHandler().name }`,);break;
      case 'ws':
        break;
      case 'rpc':
        break;
    }
    return true; }}Copy the code

Bind the guard to the AppController:

import { Controller, Get, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { AuthGuard } from './auth.guard';

@Controller(a)@UseGuards(AuthGuard)
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  @Get('/a')
  getA(): string {
    return 'A'; }}Copy the code

Start the program, try to access the service with CRUL, see the program console has the following output:

http://localhost/{} => AppController.getHello

http://localhost/a{} => AppController.getA

Around these capabilities provided by Nest, business logic operations can be implemented to protect part of the content.

Global guard

Register a guard in any module, and all controllers (globally) will pass through the guard logic.

import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';

@Module({
  providers: [{provide: APP_GUARD,
      useClass: AuthGuard,
    },
  ],
})
export class AppModule {}
Copy the code

The other option is to set it in main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { UnauthorizedFilter } from './unauthorized.filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalGuards(new AuthGuard());
  await app.listen(3000);
}
bootstrap();
Copy the code

The effect is the same, except that the former’s dependencies are managed by Nest and the latter’s are not.

Multiple guard logic

From the above source code we can see that the guard decorator can only enter multiple guard classes, such as @useGuards (Guard1,Guard2…). Obviously, the order in which they are executed depends on the order of the input, and if any of the guards returns false, the subsequent procedure terminates execution. The request procedure returns the status code of 403.

Try to bind the guards globally, class, and method, and observe the sequence of execution. The process will be to execute the global first, then the controller, and finally the method. (Interested students can clone the source code to observe the process, not repeated here)

Decorators and meta information

About the concept of guard, in fact, the above section has been finished, the number of words in this article is small, in order to push the home page here more extended content. The aforementioned context objects for guards extend the ArgumentHost interface, and getHandler() and getClass() are extended primarily to determine permissions through decorator setup related metadata. If a method requires special permissions:

  @Get('/a')
  @SetMetadata('User-Agent'['chrome'.'firefox'])
  @UseGuards(Auth2Guard)
  getA(): string {
    return 'A';
  }
Copy the code

SetMetadata() is a Nest framework method for setting metadata for functions, classes, and parameter objects. See this article for concepts. It provides a binding of the input key value to functions, classes, and parameter objects that can be retrieved as needed. More on this later when we introduce custom decorators. The above code means that the getA method is bound to a user-Agent property whose value is an array. Then we modify Auth2Guard slightly:

@Injectable(a)export class Auth2Guard implements CanActivate {
  constructor(private reflector: Reflector) {} // dependency injection

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const [request, response, next] = context.getArgs();
    return (
      this.reflector
        .get<string[] > ('user-agent', context.getHandler())
        .findIndex(
          (_r) = >
            request.headers['user-agent']
              .toLowerCase()
              .indexOf(_r.toLowerCase()) > -1) > -1); }}Copy the code

Notice the third line (which was not there before), where Reflector injection needs to be added. In line 10, the user-Agent field value is taken out and judged. You can open it in Chrome or Firefox and visit http://localhost:3000/a by command to see what the guard does. In general, custom metadata is written statically (or Hardcode) or dynamically to the associated methods according to the configuration file through custom decorators. Look forward to the next article if you are interested in this topic.

Combined with the previous chapter of Provider, permission configuration in controller (class) can be injected through configuration files or even database (class) and entrusted to Nest for dependency management, which greatly improves the flexibility of permission configuration and development efficiency.