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

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.

Related

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 use different jwt secret to verify the request?

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.

How to integrate OpenID connect to Nest JS application

I used this documentation(https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#accounts)for implementing OpenID to Nest JS. In this documentation he mentioned client_id and client secret and redirect URLS. How to get this Information's and Integrating
One option is to create an oidc strategy for passport.
It's a lengthy process, and rather than copying/pasting an entire tutorial, I'll add a link and hope it doesn't break.
https://sdoxsee.github.io/blog/2020/02/05/cats-nest-nestjs-mongo-oidc.html
Here's the strategy implementation, but there are several other components that need to be configured.
// auth/oidc.strategy.ts
import { UnauthorizedException } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Strategy, Client, UserinfoResponse, TokenSet, Issuer } from 'openid-client';
import { AuthService } from './auth.service';
export const buildOpenIdClient = async () => {
const TrustIssuer = await Issuer.discover(`${process.env.OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER}/.well-known/openid-configuration`);
const client = new TrustIssuer.Client({
client_id: process.env.OAUTH2_CLIENT_REGISTRATION_LOGIN_CLIENT_ID,
client_secret: process.env.OAUTH2_CLIENT_REGISTRATION_LOGIN_CLIENT_SECRET,
});
return client;
};
export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') {
client: Client;
constructor(private readonly authService: AuthService, client: Client) {
super({
client: client,
params: {
redirect_uri: process.env.OAUTH2_CLIENT_REGISTRATION_LOGIN_REDIRECT_URI,
scope: process.env.OAUTH2_CLIENT_REGISTRATION_LOGIN_SCOPE,
},
passReqToCallback: false,
usePKCE: false,
});
this.client = client;
}
async validate(tokenset: TokenSet): Promise<any> {
const userinfo: UserinfoResponse = await this.client.userinfo(tokenset);
try {
const id_token = tokenset.id_token
const access_token = tokenset.access_token
const refresh_token = tokenset.refresh_token
const user = {
id_token,
access_token,
refresh_token,
userinfo,
}
return user;
} catch (err) {
throw new UnauthorizedException();
}
}
}
You get the client-id and secret from the openid connect provider. You add/register the client there.
Redirect URL is the URL to the openid connect client, to what URL the authorization code should be sent to after a successful authentication. This URL is hardcoded in the provider.

NestJS / NodeJS / Passport / JWT - Stock current user

I have a NestJS backend, secured by JWT.
I would like to know what is the best way to store the actual user or the best way to pass it to my services?
I have a JwtAuthGuard
#Injectable()
export class JwtAuthGuard extends AuthGuard( 'jwt' ) {
canActivate(context: ExecutionContext) {
return super.canActivate( context );
}
handleRequest(err, user, info) {
if ( err || !user ) {
throw err || new UnauthorizedException();
}
return user;
}
}
My actual user id is in user var in handleRequest but I don't know where to "stock" it to be able to reach it in some modules.
Does anyone can help me ?
Thanks
The JWT itself is where you store the user id (or any identifying details of the user).
If you create the JWT payload with the user id ({ id: 123, ... }) the passport will set the user member to the request object.
Important: Don't store sensitive data in the JWT.
#AuthGuard( 'jwt' )
#Get('profile')
getUserId(#Request() req: any) {
return req.user.id;
}
You can pass the req.user.id to services as needed.
See: https://docs.nestjs.com/techniques/authentication#implement-protected-route-and-jwt-strategy-guards
One last thing:
If you like to have types for the request object you can do something like this
import { Request as HttpRequest } from 'express';
interface UserJwtPayload {
id: string,
}
type AuthRequest = HttpRequest & { user: UserJwtPayload }

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

Resources