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);
}
}
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);
}
}
i'm making google-auth site, and i made custom user-decorator and it can't return user.
#UseGuards(AuthGuard('jwt'))
#Get('profile')
getProfile(#UserDecorator() user: User) {
return user;
}
this is my controller's code
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const UserDecorator = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
console.log(request)
return request.user;
},
);
this is my custom user decorator.
my controller's function supposed to return user's info using by JWT (parsing)
but this function return's 401 error , so when i delete
#UseGuards(AuthGuard('jwt'))
and my custom decorator 's console.log(request) returns....->>
this kind of something strange object.
i checked with f12 button, and jwt token was exists.
If when you remove:
#UseGuards(AuthGuard('jwt'))
you don't receive the 401 error again, it means that you haven't done the login before you call the API.
I have looked into both passport-facebook and passport-facebook-token integration with NestJS. The problem is that NestJS abstracts passport implementation with its own utilities such as AuthGuard.
Because of this, ExpressJS style implementation that's documented will not work with NestJS. This for instance is not compliant with the #nestjs/passport package:
var FacebookTokenStrategy = require('passport-facebook-token');
passport.use(new FacebookTokenStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET
}, function(accessToken, refreshToken, profile, done) {
User.findOrCreate({facebookId: profile.id}, function (error, user) {
return done(error, user);
});
}
));
This blog post shows one strategy for implementing passport-facebook-token using an unfamiliar interface that isn't compliant with AuthGuard.
#Injectable()
export class FacebookStrategy {
constructor(
private readonly userService: UserService,
) {
this.init();
}
init() {
use(
new FacebookTokenStrategy(
{
clientID: <YOUR_APP_CLIENT_ID>,
clientSecret: <YOUR_APP_CLIENT_SECRET>,
fbGraphVersion: 'v3.0',
},
async (
accessToken: string,
refreshToken: string,
profile: any,
done: any,
) => {
const user = await this.userService.findOrCreate(
profile,
);
return done(null, user);
},
),
);
}
}
The problem here is that this seems to be completely unconventional to how NestJS expects you to handle a passport strategy. It is hacked together. It could break in future NestJS updates as well. There's also no exception handling here; I have no way to capture exceptions such as InternalOAuthError which gets thrown by passport-facebook-token because of the callback nature that's being utilized.
Is there a clean way to implement either one of passport-facebook or passport-facebook-token so that it'll use #nestjs/passport's validate() method? From the documentation: For each strategy, Passport will call the verify function (implemented with the validate() method in #nestjs/passport). There should be a way to pass a clientId, clientSecret in the constructor and then put the rest of the logic into the validate() method.
I would imagine the final result to look something similar to the following (this does not work):
import { Injectable } from "#nestjs/common";
import { PassportStrategy } from "#nestjs/passport";
import FacebookTokenStrategy from "passport-facebook-token";
#Injectable()
export class FacebookStrategy extends PassportStrategy(FacebookTokenStrategy, 'facebook')
{
constructor()
{
super({
clientID : 'anid', // <- Replace this with your client id
clientSecret: 'secret', // <- Replace this with your client secret
})
}
async validate(request: any, accessToken: string, refreshToken: string, profile: any, done: Function)
{
try
{
console.log(`hey we got a profile: `, profile);
const jwt: string = 'placeholderJWT'
const user =
{
jwt
}
done(null, user);
}
catch(err)
{
console.log(`got an error: `, err)
done(err, false);
}
}
}
In my particular case, I am not interested in callbackURL. I am just validating an access token that the client has forwarded to the server. I just put the above to be explicit.
Also if you are curious, the code above produces an InternalOAuthError but I have no way of capturing the exception in the strategy to see what the real problem is because it isn't implemented correctly. I know that in this particular case the access_token I am passing is invalid, if I pass a valid one, the code works. With a proper implementation though I would be able to capture the exception, inspect the error, and be able to bubble up a proper exception to the user, in this case an HTTP 401.
InternalOAuthError: Failed to fetch user profile
It seems clear that the exception is being thrown outside of the validate() method, and that's why our try/catch block is not capturing the InternalOAuthError. Handling this exception is critical for normal user experience and I am not sure what the NestJS way of handling it is in this implementation or how error handling should be done.
You're on the right track with the Strategy using extends PassportStrategy() class setup you have going. In order to catch the error from passport, you can extend the AuthGuard('facebook') and add some custom logic to handleRequest(). You can read more about it here, or take a look at this snippet from the docs:
import {
ExecutionContext,
Injectable,
UnauthorizedException,
} from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
// Add your custom authentication logic here
// for example, call super.logIn(request) to establish a session.
return super.canActivate(context);
}
handleRequest(err, user, info) {
// You can throw an exception based on either "info" or "err" arguments
if (err || !user) {
throw err || new UnauthorizedException();
}
return user;
}
}
Yes, this is using JWT instead of Facebook, but the underlying logic and handler are the same so it should still work for you.
In my case, I used to use the passport-facebook-token with older version of nest. To upgrade, the adjustment of the strategy was needed. I am also not interested in the callback url.
This is a working version with passport-facebook-token that uses nest conventions and benefits from dependency injection:
import { Injectable } from '#nestjs/common'
import { PassportStrategy } from '#nestjs/passport'
import * as FacebookTokenStrategy from 'passport-facebook-token'
import { UserService } from '../user/user.service'
import { FacebookUser } from './types'
#Injectable()
export class FacebookStrategy extends PassportStrategy(FacebookTokenStrategy, 'facebook-token') {
constructor(private userService: UserService) {
super({
clientID: process.env.FB_CLIENT_ID,
clientSecret: process.env.FB_CLIENT_SECRET,
})
}
async validate(
accessToken: string,
refreshToken: string,
profile: FacebookTokenStrategy.Profile,
done: (err: any, user: any, info?: any) => void,
): Promise<any> {
const userToInsert: FacebookUser = {
...
}
try {
const user = await this.userService.findOrCreateWithFacebook(userToInsert)
return done(null, user.id) // whatever should get to your controller
} catch (e) {
return done('error', null)
}
}
}
This creates the facebook-token that can be used in the 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.