NestJS: Adding verification options to AuthGuard with JWT

后端 未结 2 915
一整个雨季
一整个雨季 2021-02-02 18:40

I am trying to make use of the AuthGuard decorator, and the passport JWT strategy, following the documentation.

Everything in the documentation works great.

相关标签:
2条回答
  • 2021-02-02 18:54

    I tried a slightly different approach, by extending the AuthGuard guard. I wanted to maintain the ability to use different Passport Strategies, so I included a mixin. Feedback is appreciated.

    In your Jwt strategy you could simply return the JwtPaylozd so that the user has a scopes attribute. Then the custom AuthGuard looks like this:

    import { UnauthorizedException, mixin } from "@nestjs/common";
    import { AuthGuard } from "@nestjs/passport";
    
    export function AuthScopes(scopes: string[], type?: string | string[]) {
        return mixin(class ScopesAuth extends AuthGuard(type) {
            protected readonly scopes = scopes;
            handleRequest(err, user, info, context) {
            if (err || !user) {
                throw err || new UnauthorizedException();
            }
    
            if(!this.scopes.some(s => user.scopes.split(' ').includes(s)))
            {
                throw new UnauthorizedException(`JWT does not possess one of the required scopes (${this.scopes.join(',')})`);
            }
            return user;
            }
        });
      }
    

    You can then use this guard like so:

    @Get('protected')
    @UseGuards(AuthScopes(['secret:read'], 'jwt'))
    async protected(): Promise<string> {
        return 'Hello Protected World';
    }
    

    'jwt' represents the strategy.

    0 讨论(0)
  • 2021-02-02 18:58

    When you look at the code of the AuthGuard, it seems like the options.callback function is the only possible customization.

    I think instead of writing your own AuthGuard that supports scope checks, it is cleaner to have a ScopesGuard (or RolesGuard) with its own decorater like @Scopes('manage_server') instead. For this, you can just follow the RolesGuard example in the docs, which also just checks an attribute of the JWT payload under the user property in the request.


    Essential steps

    Create a @Scopes() decorator:

    export const Scopes = (...scopes: string[]) => SetMetadata('scopes', scopes);
    

    Create a ScopesGuard:

    @Injectable()
    export class ScopesGuard implements CanActivate {
      constructor(private readonly reflector: Reflector) {}
    
      canActivate(context: ExecutionContext): boolean {
        const scopes = this.reflector.get<string[]>('scopes', context.getHandler());
        if (!scopes) {
          return true;
        }
        const request = context.switchToHttp().getRequest();
        const user = request.user;
        const hasScope = () => user.scopes.some((scope) => scopes.includes(scope));
        return user && user.scopes && hasScope();
      }
    }
    

    Use the ScopesGuard as a global guard for all routes (returns true when no scopes are given):

    @Module({
      providers: [
        {
          provide: APP_GUARD,
          useClass: ScopesGuard,
        },
      ],
    })
    export class ApplicationModule {}
    

    And then use it on an endpoint:

    @Get('protected')
    @UseGuards(AuthGuard('jwt'))
    @Scopes('manage_server')
    async protected(): Promise<string> {
        return 'Hello Protected World';
    }
    
    0 讨论(0)
提交回复
热议问题