Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NestJS: Adding verification options to AuthGuard with JWT

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

Everything in the documentation works great. But I now want to protect a route with a scope contained in the JWT. So here is a basic jwt payload generated by my application:

{
  "user": {
    "id": "20189c4f-1183-4216-8b48-333ddb825de8",
    "username": "[email protected]"
  },
  "scope": [
    "manage_server"
  ],
  "iat": 1534766258,
  "exp": 1534771258,
  "iss": "15f2463d-8810-44f9-a908-801872ded159",
  "sub": "20189c4f-1183-4216-8b48-333ddb825de8",
  "jti": "078047bc-fc1f-4c35-8abe-72834f7bcc44"
}

Here is the basic protected route being guarded by the AuthGuard decorator:

@Get('protected')
@UseGuards(AuthGuard('jwt'))
async protected(): Promise<string> {
    return 'Hello Protected World';
}

I would like to add options and restrict the access of that route to the people having the manager_server scope into their JWT. So after reading a little bit of the AuthGuard code, I thought that I was able to write something like:

@Get('protected')
@UseGuards(AuthGuard('jwt', {
    scope: 'manage_server'
}))
async protected(): Promise<string> {
    return 'Hello Protected World';
}

However, I can't see in the documentation where I could make use of this option.

I thought that adding an option argument to the validate function of the JWTStrategy could make the trick, but it does not. Here is my validate function (contained in the jwt.strategy.ts file):

async validate(payload: JwtPayload, done: ((err: any, value: any) => void)) {
    const user = await this.authService.validateUser(payload);
    if (!user) {
        return done(new UnauthorizedException(), false);
    }
    done(null, user);
}

Thank you very much for your help and don't hesitate to ask me for more informations in the comments if you need so.

like image 557
Hammerbot Avatar asked Aug 20 '18 14:08

Hammerbot


People also ask

What is AuthGuard in NestJS?

Authorization guard 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.

What are strategies in NestJS?

It is a way to define custom algorithm/logic to authenticate users. Passport has a lot of strategies like JWT, facebook, google and more.. You extend a strategy and add your custom logic like from where to get the user, how to validate the user and options passed to passport.


1 Answers

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';
}
like image 98
Kim Kern Avatar answered Sep 20 '22 15:09

Kim Kern