How to use different jwt secret to verify the request? - node.js

In my Nestjs project, I am trying to use different jwt secrete to verify the request. It could try the second secrete if the first one is failed.
Here is my code:
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly configService: ConfigService,
private readonly userService: UserService
) {
super({
jwtFromRequest: ExtractJwt.fromExtractors([
(request: Request) => {
return request?.cookies?.Authentication;
},
]),
secretOrKey: configService.get('JWT_SECRET'),
});
}
}
I have tried to use try/catch, but it can not work. Any help would be appreciated.

Related

Nestjs Passport google Oauth2 with custom JWT

What I need to achieve -
I need to have a dynamic redirect URL (not google's refer Current Flow last step) based on the query param sent by Frontend.
I need to send my custom JWT token instead of google token which can have roles and permission in it. (Not sure if we can add claims to google token as well)
In my app, I have 2 roles - candidate, and recruiter. I need to use Gmail auth and create a user in my DB according to roles, which again I could achieve via query param pass by Frontend.
Current Flow -
Frontend calls google/login -> GoogleAuthGaurd -> GoogleStrategy -> google/redirect -> Generate custom JWT token -> redirect to frontend with access token and refresh token in URL.
Problem -
In Passport, we have GoogleAuthGaurd, and GoogleStrategy. I have read somewhere that Auth Gaurd decides which strategy to be used and it internally calls the strategy and further execution.
If I pass query param to google/login it totally ignores it and redirects to strategy. We can access contecxt (ExecutionContext) in AuthGaurd, so we can get query param there but how to pass it to strategy? or may be invoke custom strategy from auth guard not sure if we can.
Is there any way I could pass the query param to strategy then I could write a logic to update the redirect URI or roles?
import { TokenResponsePayload } from '#identity/payloads/responses/token-response.payload';
import { Controller, Get, Inject, Req, Res, UseGuards } from '#nestjs/common';
import { ApiTags } from '#nestjs/swagger';
import { Request, Response } from 'express';
import {
AuthServiceInterface,
AuthServiceSymbol,
} from '../interfaces/services/auth-service.interface';
import { AccessTokenGaurd } from '../utils/access-token.guard';
import { GoogleAuthGaurd } from '../utils/google-auth-guard';
import { RefreshTokenGuard } from '../utils/refresh-token.guard';
#ApiTags('Auth')
#Controller('auth')
export class AuthController {
constructor(
#Inject(AuthServiceSymbol)
private authService: AuthServiceInterface,
) {}
#Get('google/login')
#UseGuards(GoogleAuthGaurd)
handleGoogleLogin() {}
#Get('google/redirect')
#UseGuards(GoogleAuthGaurd)
async handleGoogleRedirect(#Req() req, #Res() res: Response) {
const tokens = await this.authService.signInWithGoogle(req);
res.redirect(302,`http://127.0.0.1:4200?access_token=${tokens.accessToken}&refresh_token=${tokens.refreshToken}`)
}
#Get('logout')
#UseGuards(AccessTokenGaurd)
async remove(#Req() req: Request): Promise<void> {
return this.authService.removeSession(req.user['sessionId']);
}
#UseGuards(RefreshTokenGuard)
#Get('refresh')
async refreshToken(#Req() req: Request): Promise<TokenResponsePayload> {
const sessionId = req.user['sessionId'];
const refreshToken = req.user['refreshToken'];
return this.authService.refreshTokens(sessionId, refreshToken);
}
}
import { Injectable } from '#nestjs/common'; import { AuthGuard } from '#nestjs/passport';
#Injectable() export class GoogleAuthGaurd extends AuthGuard('google') {}
import { CalConfigService, ConfigEnum } from '#cawstudios/calibrate.common';
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Profile, Strategy } from 'passport-google-oauth20';
import { VerifiedCallback } from 'passport-jwt';
const configService = new CalConfigService();
#Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
clientID: configService.get(ConfigEnum.CLIENT_ID),
clientSecret: configService.get(ConfigEnum.CLIENT_SECRET),
callbackURL: configService.get('CALLBACK_URL'),
scope: ['profile', 'email'],
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: Profile,
done: VerifiedCallback,
): Promise<any> {
const email = profile.emails[0].value;
done(null, email);
}
}

use dynamic passport strategy nestjs with multiple clientID and clientSecret

I want to implement passport google and facebook strategy using multiple keys for different apps, like get clientID or something in req params and select google clientID and clientSecret from DB on base of given param i.e users of one application can authenticate using a specific clientID and clientSecret,
want to implement something like this but not sure how to do it in nestjs as iam fairly new to nestjs.
https://medium.com/passportjs/authenticate-using-strategy-instances-49e58d96ec8c
// GoogleStrategy code
#Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor() {
super({
clientID: '', // dynamic key from multiple type of application
clientSecret: '', // dynamic key from multiple type of application
callbackURL: '', // url from user request or hardcoded
scope: ['email', 'profile'], //hardcoded or data from request
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: any,
done: VerifyCallback,
): Promise<any> {
const { name, emails, photos } = profile;
done(null, profile);
}
}
// Goole Controler
#Controller('google-auth')
export class GoogleAuthController {
constructor(private readonly googleAuthService: GoogleAuthService) {}
#Get('login')
#UseGuards(AuthGuard('google'))
login(#Param('appID') appID: string, #Req() req) {
// Query params to switch between two app type
// e.g app1ID=123132323 or app2ID=2332323
//But what now? The strategy get initiated inside the module
}
#Get('redirect')
#UseGuards(AuthGuard('google'))
redirect(#Req() req) {}
#Get('status')
status() {}
#Get('logout')
logout() {}
}
//GoogleModule
#Module({
imports: [],
controllers: [AppController],
providers: [AppService, GoogleStrategy], //How to use this strategy with multiple
// clientID and clientSecret on base of a
// param
})
export class AppModule {}

How to get access to JwtToken to check with blacklist within nestjs passport strategy?

I'm trying to check for blacklisted JWT tokens within JWTStrategy. jwtFromRequest doesn't take an async function, so I can't check it there.
validate function gives access to JWT payload and not the token.
Below is my sample code.
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly configService: ConfigService<AppJWTSettings>,
#Inject(CACHE_MANAGER) private readonly cache: Cache,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // Returns the encoded JWT string or null.
ignoreExpiration: false, // validate the expiration of the token.
// https://docs.nestjs.com/techniques/authentication#implementing-passport-jwt
// PEM-encoded public key
secretOrKey: configService.get<string>('JWT_PUBLIC_KEY'),
algorithms: ['RS256'],
});
}
/**
* Passport will build a user object based on the return value of our validate() method,
* and attach it as a property on the Request object.
*
* #param payload JWT payload
*/
async validate(payload: JwtPayload): Promise<JwtUser> {
const user = { id: payload.sub, iat: payload.iat };
return user;
}
}
There is one option with secretOrKeyProvider. Here is an example:
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly configService: ConfigService<AppJWTSettings>,
#Inject(CACHE_MANAGER) private readonly cache: Cache,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // Returns the encoded JWT string or null.
ignoreExpiration: false, // validate the expiration of the token.
// https://docs.nestjs.com/techniques/authentication#implementing-passport-jwt
// PEM-encoded public key
secretOrKeyProvider: (
_request: Request,
rawJwtToken: any,
done: (err: any, secretOrKey?: string | Buffer) => void,
) => {
// Check if your token is blocked here!!!
void isBlocked(rawJwtToken).then(isBlocked => {
if (isBlocked) {
done(new Error("This token is blocked"));
} else {
done(null, configService.get<string>('JWT_PUBLIC_KEY'));
}
});
},
algorithms: ['RS256'],
});
}
/**
* Passport will build a user object based on the return value of our validate() method,
* and attach it as a property on the Request object.
*
* #param payload JWT payload
*/
async validate(payload: JwtPayload): Promise<JwtUser> {
const user = { id: payload.sub, iat: payload.iat };
return user;
}
}
When a new token is created, I store the token in a cookie and pass the token via AJAX calls and sometimes pass it around via query string requests. You should just be able to pass whatever token the user is using via cookies (headers), query string etc... On the controller, have it validated, if blacklisted return a redirect url or string on unauthorized and redirect via JavaScript.

Nestjs + Passport: Prevent user 1 to access information of user 2

How can I prevent user 1 to access information of user 2 using passport in a Nesjs app ?
I already have 2 strategies:
the local strategy which validate a user with email/password. The route protected by this strategy return a jwt token.
the jwt strategy which validate the given jwt token.
Now, I want to restrict access to routes such as users/:id to jwt token which actually have the same userId encrypted.
How to do that ?
EDIT
I was mixing Authentication and Authorization: what I want to achieve is about authorization, once the user has been authenticated.
I had to use Guard:
own.guard.ts
#Injectable()
export class OwnGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
return req.user.id === req.params.id;
}
}
Then use it in my route:
#Get(':id')
#UseGuards(OwnGuard)
async get(#Param('id') id: string) {
return await this.usersService.get(id);
}
ORIGINAL ANSWER
What I did was to create a third strategy based on the jwt one:
#Injectable()
export class OwnStrategy extends PassportStrategy(Strategy, 'own') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: SECRET,
passReqToCallback: true
});
}
async validate(req: Request, payload: { sub: string }) {
if (req.params.id !== payload.sub) {
throw new UnauthorizedException();
}
return { userId: payload.sub };
}
}
Note how I pass the custom name 'own' as second parameter of PassportStrategy to differentiate it from the 'jwt' one. Its guard:
#Injectable()
export class OwnAuthGuard extends AuthGuard('own') {}
This works but I wonder if it is the good way of doing it...
What if later I want to able user modification for admin users ?
Should I create a forth strategy which check if role === Role.ADMIN || req.params.id === payload.sub ?
I think I'm missing something. There should be a way to create a strategy which validate only the jwt, another one only the userId, another one only the role, and combine them as I want when applying guards to my routes.
same case. you can use handleRequest method in guard.
here you can access user auth and req, then doing validation for resource appropriate. check out my code
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
return super.canActivate(context);
}
handleRequest(err, user, info, context: ExecutionContext) {
const request = context.switchToHttp().getRequest<Request>();
const params = request.params;
if (user.id !== +params.id) {
throw new ForbiddenException();
}
return user;
}
}
look more here https://docs.nestjs.com/security/authentication#extending-guards

How to manipulate cookies in Passport-JS AuthGuard with NestJS?

So, I set up the Local and JWT strategies normally, and they work wonderfully. I set the JWT cookie through the login route. I want to also set the refresh cookie token, and then be able to remove and reset the JWT token through the JWT AuthGuard, refreshing it manually and setting the ignoreExpiration flag to true.
I want to be able to manipulate the cookies through the JWT AuthGuard. I can already view them, but I can't seem to set them. Is there a way to be able to do this?
/************************
* auth.controller.ts
************************/
import { Controller, Request, Get, Post, UseGuards } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
import { AuthService } from './auth/auth.service';
import { SetCookies, CookieSettings } from '#ivorpad/nestjs-cookies-fastify';
import { ConfigService } from '#nestjs/config';
#Controller('auth')
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly configService: ConfigService,
) {}
#UseGuards(AuthGuard('local'))
#Post('login')
#SetCookies()
async login(#Request() request) {
const jwtCookieSettings = this.configService.get<CookieSettings>('shared.auth.jwtCookieSettings');
request._cookies = [{
name : jwtCookieSettings.name,
value : await this.authService.signJWT(request.user),
options: jwtCookieSettings.options,
}];
}
#UseGuards(AuthGuard('jwt'))
#Get('profile')
async getProfile(#Request() req) {
return req.user;
}
}
/************************
* jwt.strategy.ts
************************/
import { Strategy, StrategyOptions } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, Request } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly configService: ConfigService) {
super(configService.get<StrategyOptions>('shared.auth.strategy.jwt.strategyOptions'));
}
async validate(#Request() request, payload: any) {
return payload;
}
}
According to the Passport JWT Guard Configuration Docs, we can set the request to be passed to the callback, so that we may be able to control it using the validate method (this option is available with other strategies, too). Once that is done, you may view how to manipulate the cookies, as per Express (or Fastify).
For Express (which is what I am using), the method can be found in the docs:
For setting the cookies, use request.res.cookie().
For clearing the cookies, use request.res.clearCookie().

Resources