Inject Service to Guard - Nestjs - node.js

I have the following global guard:
authorization.guard.ts
import { ExecutionContext, Injectable } from "#nestjs/common"
import { Reflector } from "#nestjs/core"
import { AuthGuard } from "#nestjs/passport"
#Injectable()
export class AuthorizationGuard extends AuthGuard(["azure-ad"]) {
public constructor(private readonly reflector: Reflector) {
super()
}
async canActivate(context: ExecutionContext) {
const isPublic = this.reflector.get<boolean>(
"isPublic",
context.getHandler(),
)
if (isPublic) {
return true
}
const req = context.switchToHttp().getRequest()
if(req.headers.isbypass){
//help needed
}
const result = (await super.canActivate(context)) as boolean
await super.logIn(req)
return result
}
}
and the following auth module and strategy:
import { Module } from "#nestjs/common";
import { PassportModule } from "#nestjs/passport";
import { UsersModule } from "modules/users/users.module";
import { AzureADStrategy } from "./azureAD.strategy";
import { SessionSerializer } from "./session.serializer";
#Module({
imports: [PassportModule, UsersModule],
providers: [AzureADStrategy, SessionSerializer],
})
export class AuthModule {}
import {
BearerStrategy,
IBearerStrategyOption,
ITokenPayload,
VerifyCallback,
} from "passport-azure-ad";
import {
Inject,
Injectable,
OnModuleInit,
UnauthorizedException,
} from "#nestjs/common";
import passport = require("passport");
import { UsersService } from "modules/users/users.service";
import env from "../../config";
const tenantId = env.TENANT_ID;
const clientID = env.CLIENT_ID || "";
const azureCredentials: IBearerStrategyOption = {
//
};
#Injectable()
export class AzureADStrategy extends BearerStrategy implements OnModuleInit {
onModuleInit() {
passport.use("azure-ad", this);
}
constructor(
#Inject(UsersService) private usersService: UsersService
) {
super(
azureCredentials,
async (token: ITokenPayload, done: VerifyCallback) => {
if (Date.now() / 1000 > token.exp) {
return done(new UnauthorizedException("access token is expired"));
}
const tokenUsername = token?.preferred_username?.slice(0, 9);
const tokenAppId = !tokenUsername && token?.azp;
if (!tokenUsername && !tokenAppId) {
return done(new UnauthorizedException("Missing User"));
}
let user;
if (tokenUsername) {
try {
user = await this.usersService.getUser(
tokenUsername
);
if (!user) {
return done(
new UnauthorizedException("User is not a test user")
);
}
} catch (err) {
return done(err);
}
user.tz = tokenUsername;
} else {
user.appId = tokenAppId;
}
return done(null, user, token);
}
);
}
}
The guard is defined globally using:
const reflector = app.get(Reflector);
app.useGlobalGuards(
new AuthorizationGuard(reflector),
);
And the auth module is imported in app.module.ts:
#Module({
imports: [
AuthModule,
...
]
Now, for the question.
I would like to have a way to "backdoor" the global authorization by checking if req.headers.isbypass exists in the request's headers, and if it does use userService in authorizationGuard, so i can inject the user from the DB to req.user myself and continue the request.
How do I achieve that?

I would change the app.useGlobalGuards() to be a global guard provider, adding
{
provider: APP_GUARD,
useClass: AuthorizationGuard,
}
Into the providers of your AppModule so that Nest handles all of the DI for you. From there, it's just adding the UsersService to the constructor like you already have for the Reflector
#Injectable()
export class AuthorizationGuard extends AuthGuard(["azure-ad"]) {
public constructor(
private readonly reflector: Reflector,
private readonly usersService: UsersServce
) {
super()
}
async canActivate(context: ExecutionContext) {
const isPublic = this.reflector.get<boolean>(
"isPublic",
context.getHandler(),
)
if (isPublic) {
return true
}
const req = context.switchToHttp().getRequest()
if(req.headers.isbypass){
//help needed
}
const result = (await super.canActivate(context)) as boolean
await super.logIn(req)
return result
}
}

Related

How to inject repository into nestjs-rate-limiter guard

I've tried to inject a repository into the guard which extends from RateLimiterGuard nestjs-rate-limiter but I got an error when call super.canActivate(ctx). It said that this.reflector.get is not a function. Is there any mistake that I have?
Here is my code:
import { RateLimiterGuard, RateLimiterOptions } from 'nestjs-rate-limiter';
import type { Request } from 'express';
import config from 'config';
import { ExecutionContext, Injectable } from '#nestjs/common';
import { MockAccountRepository } from '../modules/mock/mock-accounts/accounts-mock.repository';
import { Reflector } from '#nestjs/core';
import { UserIdentifierType } from 'src/modules/users/user.types';
const ipHeader = config.get<string>('server.ipHeader');
#Injectable()
export class ForwardedIpAddressRateLimiterGuard extends RateLimiterGuard {
constructor(
reflector: Reflector,
options: RateLimiterOptions,
private readonly mockAccRepo: MockAccountRepository,
) {
super(options, reflector);
}
protected getIpFromRequest(request: Request): string {
return request.get(ipHeader) || request.ip;
}
async canActivate(ctx: ExecutionContext): Promise<boolean> {
const req = ctx.switchToHttp().getRequest();
const phoneNumber = req.body?.phoneNumber || req.params?.phoneNumber;
// If mock phone number, always allow
if (
await this.mockAccRepo.findOne({
identifier: phoneNumber,
identifierType: UserIdentifierType.PHONE_NUMBER,
})
) {
return true;
}
// Otherwise, apply rate limiting
return super.canActivate(ctx);
}
}

Authentication working fine in swagger but responding as unauthorised in frontend app,though it is storing jwt token in the cookie

here is my auth.controller.ts
import { Body, Controller, Get, Post, Req, Res, UnauthorizedException, ValidationPipe } from '#nestjs/common';
import { ApiBadRequestResponse, ApiCreatedResponse, ApiForbiddenResponse, ApiOkResponse, ApiTags, ApiUnauthorizedResponse } from '#nestjs/swagger';
import { User } from 'src/interfaces/user.interface';
import { AuthService } from './auth.service';
import { AuthCredentials } from './dto/auth-credential.dto';
import { Response,Request } from 'express';
import { JwtService } from '#nestjs/jwt';
#ApiTags('auth')
#Controller('auth')
export class AuthController {
constructor(
private authService: AuthService,
private jwtService: JwtService
){}
#Post('/signup')
#ApiCreatedResponse({description: 'this response has created successfully'})
#ApiForbiddenResponse({description: 'Forbidden'})
signUp(#Body(ValidationPipe) authCredentials:AuthCredentials):Promise<User> {
return this.authService.signUp(authCredentials);
}
#Post('/signin')
#ApiOkResponse({description:'The resource has been successfully returned'})
#ApiForbiddenResponse({description:'Invalid credintials'})
async signin(#Body(ValidationPipe) authCredentials:AuthCredentials,#Res() response: Response):Promise<{accessToken:string}> {
const token = await this.authService.signIn(authCredentials);
response
.cookie('access_token', token, {
httpOnly: true,
domain: 'localhost', // your domain here!
expires: new Date(Date.now() + 1000 * 60 * 60 * 24),
// expires: new Date(Date.now() + 60000 ),
})
.send(token);
return token;
}
#Get('/cookies')
findAll(#Req() request: Request) {
console.log(request.cookies['access_token'].accessToken);
// or
// "request.cookies['cookieKey']"
// or
// console.log(request.signedCookies);
}
// to verify the user's token
#Get('/user')
#ApiOkResponse({description:'User varified Successfully'})
#ApiUnauthorizedResponse({description:'Unauthorised User'})
async user(#Req() request: Request) {
try{
const cookies = request.cookies['access_token']
const cookie = cookies.accessToken
const data = await this.jwtService.verifyAsync(cookie)
if(!data) {
throw new UnauthorizedException()
}
const user = await this.authService.findOne(data.username)
return user;
}catch(e){throw new UnauthorizedException()}
}
// to logout by delelting the token
#ApiOkResponse({description:'User varified Successfully'})
#ApiBadRequestResponse({description:'login failed'})
#Post('logout')
async logout(#Res({passthrough: true}) response: Response) {
response.clearCookie('access_token');
return {
message: 'Logged out successfully'
}
}
}
here is my auth.module.ts
import { Module } from '#nestjs/common';
import { APP_GUARD } from '#nestjs/core';
import { JwtModule } from '#nestjs/jwt';
import { PassportModule } from '#nestjs/passport';
import { DatabaseModule } from 'src/database/database.module';
import { AuthController } from './auth.controller';
import { authProviders } from './auth.provider';
import { AuthService } from './auth.service';
import { AuthStrategy } from './auth.strategy';
import { JwtStrategy } from './jwt.strategy';
import { RolesGuard } from './roles.guard';
import { UserRepository } from './user.repository';
#Module({
imports: [
PassportModule.register({defaultStrategy:'jwt'}),
JwtModule.register({
secret: 'abcdefghijklmnop',
signOptions:{
expiresIn: 60000,
}
}),
DatabaseModule],
controllers: [AuthController],
providers: [
...authProviders,
AuthService,
UserRepository,
JwtStrategy,
AuthStrategy,
{
provide: APP_GUARD,
useClass: RolesGuard,
},
// RolesGuard
],
exports: [
JwtStrategy,
PassportModule
]
})
export class AuthModule {}
here is jwt.strategy.ts
import { PassportStrategy } from "#nestjs/passport";
import { Strategy,ExtractJwt } from "passport-jwt"
import { Inject, Injectable, UnauthorizedException } from "#nestjs/common";
import { JwtPayload } from "../interfaces/jwt-payload.interface";
import { Model } from "mongoose";
import { User } from "src/interfaces/user.interface";
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
#Inject('USER_MODEL')
private authModel: Model<User>,
){
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken('Bearer'),
secretOrKey: 'abcdefghijklmnop',
})
}
async validate(payload: JwtPayload):Promise<User>{
const { username } = payload ;
const user = await this.authModel.findOne({ username }) ;
if(!user){
throw new UnauthorizedException()
}
return user
}
// async validate(payload: any) {
// return { ...payload.user };
// }
}
here is my user.repository.ts
import { BadRequestException, Inject, Injectable, UnauthorizedException } from "#nestjs/common";
import { Model } from "mongoose";
import { User } from "src/interfaces/user.interface";
import { AuthCredentials } from "./dto/auth-credential.dto";
var bcrypt = require('bcryptjs');
#Injectable()
export class UserRepository{
constructor(
#Inject('USER_MODEL')
private authModel: Model<User>
){}
async validateUser(authCredentials):Promise<boolean> {
const username = authCredentials.username
const userExist = await this.authModel.findOne({ username})
if(userExist === null) {return true}
else {return false}
}
async validateUserPassword(authCredentials:AuthCredentials): Promise<string> {
const { username, password } = authCredentials;
const user = await this.authModel.findOne({ username})
if (user === null) {
throw new UnauthorizedException('Invalid credintials')
}else {
const isMatch = await bcrypt.compare(password, user.password)
if (isMatch) {
return user.username
}else {throw new BadRequestException('Invalid credintials')}
}
}
}
this is auth module. I've applicant, application and some other modules too which I've to authenticate. These are working fine in swagger but I've a react frontend app, Which also working partially, means it only storing the token in the cookie but whenever trying to get or access other modules they responses as 401(Unauthorised)

Nest JS - Return undefined when trying to get user context in RolesGuard

I am trying to add role guard in Nest JS API. I used Passport, Jwt authentication for this.
In my RolesGuard class I made the request and get the user from it to check the user role valid or not. I attached the code below.
roles.guard.ts
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user: User = request.user;
return this.userService.findOne(user.id).pipe(
map((user: User) => {
const hasRole = () => roles.indexOf(user.role) > -1;
let hasPermission: boolean = false;
if (hasRole()) {
hasPermission = true;
}
return user && hasPermission;
}),
);
}
Problem here is context.switchToHttp().getRequest() returns object, which is undefined. So I could not get user details from it.
After I had some research about this error I found that order of the decorators in controller can be the issue. Then I changed the order, but still problem appears as same. Bellow I added that code also.
user.controller.ts
#UseGuards(JwtAuthGuard, RolesGuard)
#hasRoles(UserRole.USER)
#Get()
findAll(): Observable<User[]> {
return this.userService.findAll();
}
-Thank you-
if you are using graphql you can make the changes to fit the code below
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { GqlExecutionContext } from '#nestjs/graphql';
import { Role } from 'src/enums/role.enum';
import { ROLES_KEY } from '../auth';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const ctx = GqlExecutionContext.create(context);
const user = ctx.getContext().req.user;
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
if you are not using graphql, make the changes to fit the code below
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
then in you controller you can do
#UseGuards(JwtAuthGuard, RolesGuard)
#hasRoles(UserRole.USER)
#Get()
findAll(): Observable<User[]> {
return this.userService.findAll();
}
finally, for all this to work add the global guard to your app.module.ts
like so
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import {MongooseModule} from '#nestjs/mongoose';
import { AppService } from './app.service';
import { ConfigModule } from '#nestjs/config';
import configuration from 'src/config/configuration';
import { RolesGuard } from 'src/auth/auth';
import { UsersModule } from '../users/users.module';
import { AdminsModule } from 'src/admin/admins.module';
import { config } from 'dotenv';
import * as Joi from 'joi';
config();
#Module({
imports: [
// other modules
UsersModule,
// configuration module
ConfigModule.forRoot({
isGlobal: true,
cache: true,
load: [configuration],
expandVariables: true,
// validate stuff with Joi
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test', 'provision')
.default('development'),
PORT: Joi.number().default(5000),
}),
validationOptions: {
// allow unknown keys (change to false to fail on unknown keys)
allowUnknown: true,
abortEarly: true,
},
}),
// connect to mongodb database
MongooseModule.forRoot(process.env.DB_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
}),
],
controllers: [AppController],
providers: [
AppService,
{
provide: 'APP_GUARD',
useClass: RolesGuard,
}
],
})
export class AppModule {}

ExecuteContext cannot get User in Class RoleGuard

import { ROLES_KEY } from './role.decorator';
import { UserRole } from '../users/role.enum';
import { CanActivate, Injectable, ExecutionContext } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const requiredRole = this.reflector.getAllAndOverride<UserRole[]>(
ROLES_KEY,
[context.getClass(), context.getHandler()],
);
if (!requiredRole) {
return true
}
const req = context.switchToHttp().getRequest();
const user = req.user;
console.log('Before');
console.log(user)
return requiredRole.some((role) => {
if(role==="ADMIN"){
console.log("True")
}
return role === 'ADMIN'
});
}
}
The reason after return role==="ADMIN" then user can log but how can i get user before return role==="ADMIN execute ?
I can get user only after the role==="ADMIN" return only
Im using nest js working on RoleGuard

How to fix unknown authentication strategy "jwt" when I inject another service into user service in nestjs

I have a user service that handles registration, login and some other functions. I have been working on the application for sometime now with over 10 modules. But I noticed each time I injected another service and I try to consume any endpoint I get this error "Unknown authentication strategy "jwt". Error on swagger is:-
Internal Server Error
Response body
{
"statusCode": 500,
"message": "Internal server error"
}
Once I remove the injected service from the user module, everything is fine again. I have been trying to fix this because I need to use this service inside the user module.
This is the jwt.Strategy.ts
import { Injectable, HttpException, HttpStatus } from "#nestjs/common";
import { PassportStrategy } from '#nestjs/passport';
import { Strategy, ExtractJwt, VerifiedCallback } from "passport-jwt";
import { AuthService } from './auth.service';
#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);
try {
if (user) {
//return user;
return done(null, user, payload.iat)
} else if (user == null) {
const Terminal = await this.authService.validatePos(payload);
return done(null, Terminal, payload.iat)
}
else {
return done(
//throw new UnauthorizedException();
new HttpException('Unauthorised access', HttpStatus.UNAUTHORIZED),
false,
);
}
} catch (error) {
return error;
}
}
}
This is the AuthModule
import { Module } from '#nestjs/common';
import { AuthService } from './auth.service';
import { UserService } from '../user/user.service';
import { UserSchema } from '../user/user.schema';
import { MongooseModule } from '#nestjs/mongoose';
import { JwtStrategy } from './jwt.strategy';
import { ActivityLogService } from '../activity-log/activity-log.service';
import { ApiKeyStrategy } from './apiKey.strategy';
import { PassportModule } from "#nestjs/passport";
#Module({
imports: [MongooseModule.forFeature([{ name: 'User', schema: UserSchema }]),
PassportModule.register({
secret: "mysec"
}),
ActivityLogService],
providers: [AuthService, UserService, JwtStrategy, ApiKeyStrategy, ActivityLogService],
exports: [AuthService],
controllers: []
})
export class AuthModule { }
This is the api.strategy.ts
import { HeaderAPIKeyStrategy } from 'passport-headerapikey';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, HttpException, HttpStatus } from '#nestjs/common';
import { AuthService } from './auth.service';
#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);
});
}
}
This is the authService
import { Injectable } from '#nestjs/common';
import { sign } from 'jsonwebtoken';
import { UserService } from '../user/user.service';
import { TerminalService } from '../terminal/terminal.service';
import { InjectModel } from '#nestjs/mongoose';
import { Model } from 'mongoose';
import { Terminal } from '../terminal/interfaces/terminal.interface';
#Injectable()
export class AuthService {
constructor(private userService: UserService, #InjectModel('Terminal') private terminalModel: Model<Terminal>,) { }
//generate token for user
async signPayLoad(payload: any) {
return sign(payload, process.env.SECRETKEY, { expiresIn: '1h' });
}
//find user with payload
async validateUser(payload: any) {
const returnuser = await this.userService.findByPayLoad(payload);
return returnuser;
}
//generate token for Posy
async signPosPayLoad(payload: any) {
return sign(payload, process.env.SECRETKEY, { expiresIn: '24h' });
}
//find terminal with payload
async validatePos(payload: any) {
const { terminalId } = payload;
const terminal = await this.terminalModel.findById(terminalId);
return terminal;
}
validateApiKey(apiKey: string) {
const keys = process.env.API_KEYS;
const apiKeys = keys.split(',');
return apiKeys.find(key => apiKey === key);
}
}
This is the user service
import { Injectable, HttpException, HttpStatus, Inject } from '#nestjs/common';
import { User } from './interfaces/user.interface';
import { Model } from 'mongoose';
import { InjectModel } from '#nestjs/mongoose';
import { LoginUserDto } from './login-user.dto';
import { ActivityLogService } from '../activity-log/activity-log.service';
import { UpdateUserDTO } from './dtos/update_user.dto';
#Injectable()
export class UserService {
constructor(#InjectModel('User') private userModel: Model<User>,
private activityLogService: ActivityLogService,
) { }
//Login user
private users = [
{
"userId": 1,
"name": "John Doe",
"username": "john",
"password": "john123"
},
]
async login(loginDTO: LoginUserDto) {
const { email, password } = loginDTO;
return await this.users.find(users => users.username == email)
}
async findByPayLoad(payload: any) {
const { userId } = payload;
return await this.userModel.findById(userId)
}
async getAllUser() {
return this.users;
}
}
I cant figure out what I am doing wrong
Besides the code being difficult to manipulate and determine what's happening, services being in places they shouldn't and re-instantiations of services all over the place, the culprit as to why you are getting the error is simply because you never register the PassportModule from #nestjs/passport
EDIT 1/7/2017
Coming back to this answer about a year and a half later, it looks like the real issue is the use of a REQUEST scoped provider in the strategy. The nest docs explicitly mention this can't be done directly but also have a workaround for it.

Resources