docs.nestjs.com icon indicating copy to clipboard operation
docs.nestjs.com copied to clipboard

Recipe: guards + passport + roles + custom decorator combined together

Open omermorad opened this issue 4 years ago • 0 comments

I'm submitting a...

  • [ ] Regression
  • [ ] Bug report
  • [ ] Feature request
  • [x] Documentation issue or request (new chapter/page)
  • [ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior

Currently there is a documentation about how to work with Guards, how to work with Passport and how to work with roles and permissions. But none of this examples show how to combine the use of metadata and jwt payload together.

Of course you can explore the docs and get things together, but I must admin that as an experienced developer with typescript and NestJS it took me some time to understand how to get all this things together.

There are docs for authorization, for authentication and event for RABC, but not combined with JWT.

Expected behavior

Have a recipe that shows how to combine the whole three together: authorization + roles and custom metadata + passport

What is the motivation / use case for changing the behavior?

To supply a clear way of how to combine all those things together. What helped me a lot is this thread from Stackoverflow, answered by, well, not surpassingly - @jmcdo29 😎

I can create a PR writing this recipe in more detail if you want too :)

However, here is the end result of the example I was a little bit struggling with:

export const ROLES_KEY = 'roles';

export function Authorize(...roles: Role[]): MethodDecorator {
  return applyDecorators(SetMetadata(ROLES_KEY, roles), UseGuards(JwtAuthGuard));
}

@Injectable()
export class JwtAuthGuard extends PassportStrategy(Strategy, 'jwt-auth-guard') {
  public constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: <TOKEN>,
    });
  }

  validate(payload: AuthJwtPayload): ResultPayload {
     // logic
     // No access to metadata here cause no exec context
      return { .. some object here .. }
  }
}

@Injectable()
export class BaseAuthGuard extends AuthGuard('jwt-auth-guard') {
  public constructor(private readonly reflector: Reflector) {}

  public async canActivate(context: ExecutionContext): Promise<boolean> {
    await super.canActivate(context); // Must call the super

    const { user: payload } = context.switchToHttp().getRequest();

    const declaredRoles= this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

   // Do something here that needs the payload + the metadata

    return declaredRoles.some((role) => payload.roles?.includes(role));
  }
}


omermorad avatar Dec 15 '21 08:12 omermorad