Recipe: guards + passport + roles + custom decorator combined together
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));
}
}