user is undefined in request pipeline - Nestjs - 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();
}

Related

How to check for roles in NestJS Guard

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

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')}`;
}

Nestjs how to pass data from AuthGuard to controller

I have two microservices one for authentication and another for users. I can log in and get a token, and i can use protected routes only when logged in. However I want to use the userId which i get in the AuthGuard's canActivate function, but i cant reach it in the controller. What is the best way to do it?
My auth guard:
import { CanActivate, ExecutionContext, Inject, Logger } from '#nestjs/common';
import { ClientProxy } from '#nestjs/microservices';
export class JwtAuthGuard implements CanActivate {
constructor(
#Inject('AUTH_CLIENT')
private readonly client: ClientProxy,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
try {
const res = await this.client
.send(
{ role: 'auth', cmd: 'check' },
{ jwt: req.headers['authorization']?.split(' ')[1] },
)
.toPromise<boolean>();
return res;
} catch (err) {
Logger.error(err);
return false;
}
}
}
The controller:
#UseGuards(JwtAuthGuard)
#Get('greet')
async greet(#Request() req): Promise<string> {
return 'AUTHENTICATED!' + req;
}
The response:
AUTHENTICATED![object Object]
Attach the userId that you get in the AuthGuard to the req object and then you can access it in the controller:
// after fetching the auth user in the AuthGuard, attach its ID like this
req.userId = authUser.id
And in the controller, you can access it like this:
#UseGuards(JwtAuthGuard)
#Get('greet')
async greet(#Request() req): Promise<string> {
return 'AUTHENTICATED USER ID!' + req.userId;
}

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);
}
}

NestJS Authentification with JWT Passport not working

I am trying to set up a very simple login system using jwt-passport with nestjs. I followed this tutorial: https://docs.nestjs.com/techniques/authentication but I just can't get it to work. I am really new to this stuff and would appreciate if anyone can show me the way.
The way I send the login to the server:
this.clientAuthService.login(this.userName, this.password).then(response => {
this.clientAuthService.setToken(response.access_token);
this.router.navigate(['/backend']);
});
My ClientAuthService:
export class ClientAuthService {
constructor(private http: HttpClient, #Inject(PLATFORM_ID) private platformId) {
}
getToken(): string {
if (isPlatformBrowser(this.platformId)) {
return localStorage.getItem(TOKEN_NAME);
} else {
return '';
}
}
setToken(token: string): void {
if (isPlatformBrowser(this.platformId)) {
localStorage.setItem(TOKEN_NAME, token);
}
}
removeToken() {
if (isPlatformBrowser(this.platformId)) {
localStorage.removeItem(TOKEN_NAME);
}
}
getTokenExpirationDate(token: string): Date {
const decoded = jwt_decode(token);
if (decoded.exp === undefined) {
return null;
}
const date = new Date(0);
date.setUTCSeconds(decoded.exp);
return date;
}
isTokenExpired(token?: string): boolean {
if (!token) {
token = this.getToken();
}
if (!token) {
return true;
}
const date = this.getTokenExpirationDate(token);
if (date === undefined) {
return false;
}
return !(date.valueOf() > new Date().valueOf());
}
login(userName: string, password: string): Promise<any> {
const loginData = {username: userName, password};
return this.http
.post(Constants.hdaApiUrl + 'user/login', loginData, {headers: new HttpHeaders({'Content-Type': 'application/json'})})
.toPromise();
}
}
My user.controller.ts
#Controller('user')
export class UserController {
constructor(private readonly authService: AuthService) {
}
#UseGuards(AuthGuard('local'))
#Post('login')
authenticate(#Request() req) {
return this.authService.login(req);
}
}
My user.service.ts
export class UsersService {
private readonly users: User[];
constructor() {
this.users = [
{
userId: 1,
username: 'test',
password: '12345',
}
];
}
async findOne(username: string): Promise<User | undefined> {
return this.users.find(user => user.username === username);
}
}
Then I have the jwt.strategy.ts
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: Constants.jwtSecret,
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
and the local.strategy.ts
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
Mostly I just followed the tutorial and added some stuff for the client-side by myself.
I missed the part with the UseGuard('local') for the login route but after I added it I am getting 401 error always.
When I don't use UseGuard('local') it doesn't matter what I type in the login form. After I submit the details, I get access to the backend even tho it was not correct.
Also, it might be worth to mention that the validate methods in jwt.strategy.ts and local.strategy.ts are marked as not used in WebStorm.
I know its a lot of code here but I need help because I cannot find any other sources for NestJS auth configuration which is up to date. It feels like the tutorial I followed missed a lot of steps for beginners.
Make sure your post body (payload) is identical to the signature of the validate method (it actually has to be username & password).
In order to add a bit more context, in the above picture you can find detail where the implementation of the specified signature is mentioned. So it's mandatory to send exact "username" and "password" properties in the body.
Note: if you want to customize the signature of your local strategy service you simply have to pass a new object within the super() for specifying the new properties:
constructor() {
super({ usernameField: 'email' });
}
Ref: https://docs.nestjs.com/techniques/authentication
For me, in multiple projects,
Using
export class JwtStrategy extends PassportStrategy(Strategy, 'theJwt')
with #UseGuards(AuthGuard('theJwt'))
rather than using:
export class JwtStrategy extends PassportStrategy(Strategy)
with
#UseGuards(AuthGuard(JwtStrategy )), which did not work.

Resources