How to check for roles in NestJS Guard - nestjs

I have an external service providing a JWT token. In Nestjs i first have JwtGuard class:
#Injectable()
export class JwtGuard extends AuthGuard('JWT_STRATEGY') {
constructor() {
super();
}
getRequest(context: ExecutionContext) {
console.log('JwtGuard');
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
and then a passport strategy:
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'JWT_STRATEGY') {
constructor(private configService: ConfigService) {
super({
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: configService.get<string>('ADFS_KEYS_URL'),
}),
ignoreExpiration: false,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
audience: configService.get<string>('ADFS_AUDIENCE'),
issuer: configService.get<string>('ADFS_ISSUER'),
algorithms: ['RS256'],
});
}
validate(payload: unknown): unknown {
console.log('jwt strategy');
console.log(payload);
return payload;
}
}
It seems that JwtGuard is running first, then the strategy. But if i want to do additional guards and checks, say for roles. Where does one do that? Do i need another guard that runs after the passport strategy? I have two roles "User" and "Admin".

First of all, define a global guard (called RolesGuard) in the AppModule as following:
providers: [
AppService,
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
{
provide: APP_GUARD,
useClass: RolesGuard,
},
]
Then within RolesGuard we have the following:
export enum FamilyRole {
Admin = 'Admin',
User = 'User',
}
...
export class FamilyRolesGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const requiredRoles = this.reflector.getAllAndOverride<FamilyRole>(
ROLES_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
// do the rest and return either true or false
}
}
Then create your own decorator and you can decorate your APIs if you need that API to protect your app based on your guard.
import { SetMetadata } from '#nestjs/common';
export const ROLES_KEY = 'FamilyRoles';
export const FamilyRoles = (...roles: FamilyRole[]) =>
SetMetadata(ROLES_KEY, roles);
Then you can use your decorator in your API like this:
#Post('user')
#FamilyRoles(FamilyRole.Admin)
...
So, in your API, if you won't have FamilyRoles, in the guard you won't have requiredRoles, and the if block will return true.
More info: https://docs.nestjs.com/security/authorization

Related

how to inject my services layer into Auth Guard? NestJS - AuthGuards

I am creating an authentication system via UseGuards, but how can I inject dependencies into my guards?
I'd like to do it in a global way, to avoid repeating code and every controller importing the injections.
I am using the mode of dependency inversion and injecting the classes, I am also making the classes only depend on implementations and interface rules...
My AuthServices
export class AuthServices implements IMiddlewareAuth {
constructor(
private readonly jwt: IJWTDecrypt,
private readonly authUserRepo: IAuthUserContract,
) {}
public async intercept(
req: IMiddlewareAuth.Attrs,
): Promise<IMiddlewareAuth.Return> {
const token = req.headers['Authorization'];
if (!token) {
new Fails('Access denied', 403);
}
const parts = token.split(' ');
if (parts.length !== 2) {
return new Fails('Anauthorized', 401);
}
const [scheme, access] = parts;
if (!/^Bearer$/i.test(scheme)) {
return new Fails('Anauthorized', 400);
}
const id = await this.jwt.decrypt(access);
if (!id) {
return new Fails('Anauthorized', 400);
}
const user = await this.authUserRepo.findById(id);
if (!user) {
return new Fails('Anauthorized', 400);
}
return user;
}
}
my Auth.Guards
#Injectable()
export class AuthorizationGuard implements CanActivate {
constructor(
#Inject('AuthServices')
private authServices: IMiddlewareAuth,
) {}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const user = await this.authServices.intercept(request);
if (user instanceof Fails) {
throw new HttpException(
{
statusCode: user.statusCode,
error: user.message,
},
user.statusCode,
);
}
request.user = {
email: user.email,
id: user.id,
name: user.name,
} as ISessionLogin;
return true;
}
}
my Auth.Module
#Module({
imports: [ConfigModule.forRoot(), TypeOrmModule.forFeature([UserEntity])],
providers: [
{
provide: AuthUserRepository,
useFactory: (dataSource: DataSource) => {
return new AuthUserRepository(
dataSource.getMongoRepository(UserEntity),
);
},
inject: [getDataSourceToken()],
},
{
provide: JsonWebToken,
useFactory: () => {
return new JsonWebToken();
},
},
{
provide: AuthServices,
useFactory: (
jwt: JsonWebToken,
authUserRepository: AuthUserRepository,
) => {
return new AuthServices(jwt, authUserRepository);
},
inject: [JsonWebToken, AuthUserRepository],
},
],
exports: [AuthServices],
})
export class AuthModule {}
Error
Use #Inject(AuthService) instead of #Inject('AuthService'). The important distinction is that 'AuthService' is a string token that matches AuthService.name and AuthService is a class reference. Also, make sure the CreateUsersModule has AuthModule in its imports array

Nestjs with PassportModule custom AuthGuard inject ConfigService?

i want know how to inject ConfigService in PassportModule AuthGuard with custom params?
here is my code
auth.module.ts
#Module({
imports: [
PassportModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService): Promise<IAuthModuleOptions> => {
return {}
}
}),
],
// ...
})
export class AuthModule {}
github-auth.guard.ts
#Controller('auth')
export class AuthController {
// ...
#Get('github-oauth')
#UseGuards(new GithubAuthGuard({}))
async githubOAuth(#Req() req: Request, #Res() res: Response) {
const user = req.user
return await this.handleOauth(user, req, res)
}
}
github-auth.guard.ts
import { ConfigService } from '#nestjs/config'
import { AuthGuard } from '#nestjs/passport'
#Injectable()
export class GithubAuthGuard extends AuthGuard('github') {
constructor(private readonly configService: ConfigService) {
super()
}
//...
}
i call the UseGuards with new GithubAuthGuard({}) because i want pass custom params.
Update:
AuthGuard('github') return a wraped class can accept options the then pass down to my custom strategy's authenticate function as the second argument.
here is my github.strategy.ts
import { Strategy } from 'passport'
class StrategyFoo extends Strategy {
constructor(options, verify) {
//...
}
// options from AuthGuard('github')
authenticate(req, options) {
const self = this
const redirect_uri = options.callbackURL || this._options.callbackURL
// ...
}
}
#Injectable()
export class GithubBarStrategy extends PassportStrategy(StrategyFoo, 'github') {
//...
}
export const GithubStrategy = GithubBarStrategy
after some research i figure it out
Nest will not inject anything on a manually instantiated class
so just call #UseGuards(GithubAuthGuard) and then inject ConfigService in github-auth.guard.ts or github.strategy.ts like:
inject in github-auth.guard.ts
#Injectable()
export class GithubAuthGuard extends AuthGuard('github') {
// #Inject(ConfigService)
// private configService: ConfigService
constructor(private readonly configService: ConfigService) {
//
const options = {
callbackURL: configService.get('OAuth.github.callbackURL'),
//...
}
super(options)
}
getAuthenticateOptions(context) {
// this also works
return {
callbackURL: this.configService.get('OAuth.github.callbackURL'),
//...
}
}
// ...
}
or
inject in github.strategy.ts
class StrategyFoo extends Strategy {
private _options: any
constructor(options, verify) {
//...
this._options = options
// ...
}
authenticate(req, options) {
// ...
let _options = {
...options,
...this._options
}
// ...
}
}
#Injectable()
export class GithubBarStrategy extends PassportStrategy(StrategyFoo, 'github') {
constructor(private readonly configService: ConfigService) {
super({
passReqToCallback: true,
callbackURL: this.configService.get('OAuth.github.callbackURL'),
// ...
})
}
// ...
}

user is undefined in request pipeline - Nestjs

I have a controller which has an authentication guard and a RBAC authorization guard
#Get('get-framework-lists')
#UseGuards(JwtAuthGuard) // authentication guard
#Roles(Role.SO) // RBAC authorization guard
getFrameworkListsByCompany() {
return this.dashboardService.getFrameworkListsByCompany();
}
JwtAuthGuard look like this -
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(#InjectModel(User.name) private userModel: Model<UserDocument>) {
super({
ignoreExpiration: false,
secretOrKey: 'SECRET',
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
});
}
async validate(payload: any) {
const user = await this.userModel.findById(payload.sub);
return {
_id: payload.sub,
name: payload.name,
...user,
};
}
}
I have created a custom Roles.guard.ts for #Roles decorator
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRole = this.reflector.getAllAndOverride<Role>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRole) {
return true;
}
console.log({ requiredRole });
const { user } = context.switchToHttp().getRequest();
return requiredRole === user.role;
}
}
In the controller, I can access req.user as user is added to the request object.
However, I am not getting the user as undefined in roles.guard.ts.
What am I doing wrong here?
I think that simple add RolesGuard inside the #UseGuards() decorator, so that both guards can run, will solve your problem.
Like this:
#Get('get-framework-lists')
#UseGuards(JwtAuthGuard, RolesGuard) // here is the change
#Roles(Role.SO)
getFrameworkListsByCompany() {
return this.dashboardService.getFrameworkListsByCompany();
}

request undefined when extracting JWT

I have followed this guide in an attempt to get JWT authentication working.
The only difference I have is that I keep JWT Token in HttpOnly cookie which means a custom extractor is required.
I found an example of how to extract a Token from a cookie. So, the only difference is:
jwtFromRequest: ExtractJwt.fromExtractors([(req: Request) => {
return req?.cookies?.access_token
}])
Unfortunately, req is undefined for no apparent reason.
That's how my auth.module.ts looks like:
#Module({
imports: [
PassportModule,
JwtModule.register({
secret: 'qweqweqweqeqwe',
signOptions: { expiresIn: '20s' }
})
],
providers: [
AuthService,
AuthResolver,
JwtAuthGuard,
JwtStrategy
]
})
export class AuthModule { }
I have also created a strategy file jwt.stragegy.ts:
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromExtractors([(req: Request) => {
return req?.cookies?.access_token
}]),
ignoreExpiration: false,
secretOrKey: 'qweqweqweqeqwe',
})
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username }
}
}
auth.guard.ts:
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
Could it be an error on passport.js library's side? Like, #nestjs/passport fails to map arguments or something...
By following the docs, you should be able to get the request.
import { ExecutionContext } from '#nestjs/common';
import { GqlExecutionContext } from '#nestjs/graphql';
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
In my scenario what was making the payload.sub undefined was the login function on auth.service.
Before (not working):
async login(user: any) {
const payload = { apiKey: user.apiKey, sub: user.userId };
const token = this.jwtService.sign(payload);
return `Access token=${token};\n HttpOnly;\n Path=/;\n Max-
Age=${this.configService.get('jwtConstants.expirationTime')}`;
}
After changes (working):
async login(user: any) {
const payload = { apiKey: user._doc.apiKey, sub: user._doc._id };
const token = this.jwtService.sign(payload);
return `Access token=${token};\n HttpOnly;\n Path=/;\n Max-Ag
e=${this.configService.get('jwtConstants.expirationTime')}`;
}

How to implement multiple passport jwt authentication strategies in Nestjs

I have an existing authentication for users which is already working fine. The token for user authentication expires within an hour.
I want to implement another separate authentication strategy a third API that is consuming my Nestjs API. There are separate endpoints for the third-party API, the token should expire with 24 hours. The API has to stay connected to my app for 24 hours.
I don't mind using additional package to achieve this.
I also need to create a guard called thirdParty Guard so that the 3rd part API alone will have access to that endpoint.
This is my jwt.strategy.ts
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.SECRETKEY
});
}
async validate(payload: any, done: VerifiedCallback) {
const user = await this.authService.validateUser(payload);
if (!user) {
return done(
new HttpException('Unauthorised access', HttpStatus.UNAUTHORIZED),
false,
);
}
//return user;
return done(null, user, payload.iat)
}
}
ApiKey.strategy.ts
#Injectable()
export class ApiKeyStrategy extends PassportStrategy(HeaderAPIKeyStrategy) {
constructor(private authService: AuthService) {
super({
header: 'api_key',
prefix: ''
}, true,
(apikey: string, done: any, req: any, next: () => void) => {
const checkKey = this.authService.validateApiKey(apikey);
if (!checkKey) {
return done(
new HttpException('Unauthorized access, verify the token is correct', HttpStatus.UNAUTHORIZED),
false,
);
}
return done(null, true, next);
});
}
}
and this is the auth.service.ts
#Injectable()
export class AuthService {
constructor(private userService: UserService) { }
async signPayLoad(payload: any) {
return sign(payload, process.env.SECRETKEY, { expiresIn: '1h' });
}
async validateUser(payload: any) {
const returnuser = await this.userService.findByPayLoad(payload);
return returnuser;
}
validateApiKey(apiKey: string) {
const keys = process.env.API_KEYS;
const apiKeys = keys.split(',');
return apiKeys.find(key => apiKey === key);
}
}
With the above setup, If you are using Passport-HeaderAPIKey then try adding headerapikey in the Guard. The below code worked for me.
Ref: NestJS extending guard
import { ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { AuthGuard as NestAuthGuard } from '#nestjs/passport';
#Injectable()
export class AuthGuard extends NestAuthGuard(['jwt', 'headerapikey']) {
constructor(private readonly reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
return super.canActivate(context);
}
}

Resources