stoniemahonie
stoniemahonie

Reputation: 451

Authentication & Roles with Guards/Decorators: How to pass user object?

With the help of Guards/Decorators I try to check a JWT first and then the roles a user has.

I have read the documentation regarding Authentication, Guards and Decorators and understand the principles behind them.

However, what I cannot do is to somehow make the authenticated user from JWT-Guard available to Roles-Guards.

In every example that I found, exactly this part that is interesting for me is skipped / left out...

Grateful for every tip!

This is my latest try:

jwt.strategy.ts

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { JwtPayload } from './jwt.model';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      passReqToCallback: true,
      ignoreExpiration: false,
      secretOrKey: '0000',
      expiresIn: '3 days'
    });
  }

  async validate(payload: JwtPayload) {
    return {
      id: payload.id,
      email: payload.email,
      username: payload.username
    };
  }
}

roles.guard.ts

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {
  }

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get<string[]>('roles', context.getHandler());

    if (!roles) {
      return false;
    }

    const request = context.switchToHttp().getRequest();
    const user = request.user ??? // THIS is what is missing

    return roles.some((role) => {
      return role === user.role;
    });
  }
}

roles.decorator.ts

import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

users.controller.ts

@UseGuards(AuthGuard('jwt'))
@Roles('admin', 'member')
@Get('/')
async doSomething(@Req() req): Promise<User> {
  return await this.usersService.doSomething(req.user.id);
}

Upvotes: 2

Views: 10342

Answers (1)

Arne
Arne

Reputation: 432

Your decorator and guards look fine, but from the snippet of your users.controller.ts file it is not clear whether the roles guard is actually applied for the GET / route.

I do, however, have an NestJS app with a quite similar setup based on the guards documentation. The following code in users.controller.ts works as intended:

@UseGuards(JwtAuthGuard, RolesGuard)
@Controller('/users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  @Roles(UserRole.ADMIN)
  public async index(): Promise<User[]> {
    return this.userService.findAll();
  }

  // ...
}

Note how both the auth and roles guard are activated in the same scope and that JwtAuthGuard is added before RolesGuard. If I were to change the sequence of the guards then the RolesGuard would not be able to retrieve the user of the request.

Also, you might want to have a look at a similar question from some time ago which contains some details on the order of guards in different scopes.

Upvotes: 7

Related Questions