How to validate dto with class-validator before passing to passportjs AuthGuard - nestjs

How to validate dto with class-validator before passing to passport AuthGuard? I wanna use class-validator for validating incoming dto, but I see "unathorized" instead of bad request exception because pipes are being evaluated after guards in nestjs. How can I change this behaviour or I have to validate dto right in auth guard?

It's not very in NestJS way, but probably the single option to use is validate DTO inside strategy, that your guard is using:
import { PassportStrategy } from '#nestjs/passport';
import { Strategy } from 'passport-strategy';
import { validate } from 'class-validator';
import { BadRequestException } from '#nestjs/common';
class YourStrategy extends PassportStrategy(Strategy) {
authenticate(req, options) {
const errors = validate(req.body);
if (errors) {
throw new BadRequestException({ errors });
}
super.authenticate(req, options);
}
}

Related

Handling HTTP Errors inside services

I'm learning Nest, but there is a practice that i don't really like even in the official tutorial. It's one of Handling HTTP specific errors inside services. If later, for some services i'll used a protocol other that HTTP that will use a Service that handle specific HTTP errors, it don't think it's a best practice. As I'm not yet a Nestjs expert, here is how i'm trying to handle this situation:
// errors.interface.ts
export interface IError {
errorCode: number;
errorMessage: string;
}
import { Injectable } from '#nestjs/common';
import { IError } from './errors.interface';
#Injectable()
export class UserService {
// ...
async remove(id: number): Promise<Partial<User> | IError> {
const user = await this.userRepository.findOne({ where: { id } });
if (!user) {
return { errorCode: 404, errorMessage: 'user not found' };
}
await this.userRepository.remove(user);
return {
id,
};
}
}```
Here is my controller.
```// user.controller.ts
import { Controller, Get, HttpException, HttpStatus } from '#nestjs/common';
import { UserService } from './user.service';
import { IError } from './errors.interface';
#Controller('users')
export class UserController {
constructor(private userService: UserService) {}
#Get(':id')
async remove(#Param('id') id: number) {
const result = await this.userService.remove(id);
if ('errorCode' in result) {
throw new HttpException(result.errorMessage, result.errorCode);
}
return result;
}
}
As you can see, I'm trying to handle HTTP-specific errors inside HTTP controllers.
I don't have enough experience with Nestjs, maybe there are better ways to tackle these kinds of problems. I would like to know what is the best practice.
To create a Nest application instance, we use the core NestFactory class. NestFactory exposes a few static methods that allow creating an application instance. The create() method returns an application object, which fulfills the INestApplication interface. This object provides a set of methods which are described in the coming chapters. In the main.ts example above, we simply start up our HTTP listener, which lets the application await inbound HTTP requests.
so you can only work with HTTP requests and handling HTTP specific errors inside services is in fact the best practice.

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

class-validator doesn't validate data

I installed class-validator, class-transformer and in my login method I use validation, but it does not work.
This is the method that I'm using:
#Post('login')
async login(#Body() userReq:LoginDto){
return await this.userService.login(userReq)
}
This the login DTO:
import { IsEmail, IsNotEmpty } from "class-validator";
export class LoginDto {
#IsEmail() #IsNotEmpty({message:"email !!!"})
email:string;
password:string;
}
When I request insomnia without email, instead having a Bad Request Exception, I have the Not Found undefined (email is undefined).
emitDecoratorMetadata: true is enabled.
Happy for any help.

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().

I'm using a passport-jwt auth strategy in my nestJS app (with authGuard), how to get access to the token payload in my controller?

I'm trying to get access to the jwt payload in a route that is protected by an AuthGuard.
I'm using passport-jwt and the token payload is the email of the user.
I could achieve this by runing the code bellow:
import {
Controller,
Headers,
Post,
UseGuards,
} from '#nestjs/common';
import { JwtService } from '#nestjs/jwt';
import { AuthGuard } from '#nestjs/passport';
#Post()
#UseGuards(AuthGuard())
async create(#Headers() headers: any) {
Logger.log(this.jwtService.decode(headers.authorization.split(' ')[1]));
}
I want to know if there's a better way to do it?
Your JwtStrategy has a validate method. Here you have access to the JwtPayload. The return value of this method will be attached to the request (by default under the property user). So you can return whatever you need from the payload here:
async validate(payload: JwtPayload) {
// You can fetch additional information if needed
const user = await this.userService.findUser(payload);
if (!user) {
throw new UnauthorizedException();
}
return {user, email: payload.email};
}
And then access it in you controller by injecting the request:
#Post()
#UseGuards(AuthGuard())
async create(#Req() request) {
Logger.log(req.user.email);
}
You can make this more convenient by creating a custom decorator:
import { createParamDecorator } from '#nestjs/common';
export const User = createParamDecorator((data, req) => {
return req.user;
});
and then inject #User instead of #Req.

Resources