Reputation: 41
I am new to NestJs and trying to implement Google Sign in
using passport-google-oauth20
package. I have followed that blog to implement google sign in. Through this package I am able to successfully signed-in and able to get access_token
but I need id_token
instead of access_token
. I dug into the passport-google-oauth20
Strategy
class and there I can see different overloaded constructors where one overloaded constructor contains params argument of type GoogleCallbackParameters
which contains optional id_token
field. But don't know how to make that constructor called. Tried different ways but with no success. :(
Below is my code,
import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Request } from "express";
import { Profile } from "passport";
import {
GoogleCallbackParameters,
Strategy,
VerifyCallback,
} from "passport-google-oauth20";
import { googleStrategy } from "src/utils/constants";
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, "google") {
constructor() {
super({
clientID:
process.env.BACKEND_ENV === "dev"
? googleStrategy.GOOGLE_CLIENT_ID
: process.env.GOOGLE_CLIENT_ID,
clientSecret:
process.env.BACKEND_ENV === "dev"
? googleStrategy.GOOGLE_CLIENT_SECRET
: process.env.GOOGLE_CLIENT_SECRET,
callbackURL:
process.env.BACKEND_ENV === "dev"
? googleStrategy.GOOGLE_CALLBACK_URL
: process.env.GOOGLE_CALLBACK_URL,
scope: ["email", "profile", "openid"],
passReqToCallback: true,
});
}
async validate(
req: Request,
accessToken: string,
refreshToken: string,
params: GoogleCallbackParameters,
profile: Profile,
done: VerifyCallback,
): Promise<any> {
const { name, emails, photos } = profile;
const user = {
email: emails[0].value,
firstName: name.givenName,
lastName: name.familyName,
picture: photos[0].value,
accessToken,
refreshToken,
};
done(null, user);
}
}
As you can see for getting Request
, I have mentoned passReqToCallback: true
option and in the validate method I am getting the Request
object but don't know how to make params
of type GoogleCallbackParameters
get filled with the required object.
Thanks.
Upvotes: 3
Views: 1048
Reputation: 1
After a while digging into this problem, I think I can give anyone interested some information.
Using export class GoogleStrategy extends PassportStrategy(Strategy, 'google', 5)
allows using validate
normally without callback function in super. 5 is number of parameter of validate function.
validate
function work?It's actually work. However, passport-google-oauth20
has 4 overloaded versions of validate
function. Library decides the version by using number of parameter of callback (function.length). Using validate
in Nestjs always use
(accessToken: string, refreshToken: string, profile: Profile, verified: VerifyCallback) => void
Because Nestjs pass to passport-google-oauth20
a callback with unknown length (..args), so the function.length is 0. With this, validate using default version above.
1. Pass a callback function into strategy constructor (walk-around)
Passing callback function as second parameter in super constructor
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
// ...
super({
// options
}, async() => {});
}
then passport-google-oauth20 Strategy
will receives 3 argument (option, our callback, nestjs callback)
. It uses 2 (options
and verify
), so our callback will replace Nestjs callback. Our function has explicit number of parameters so things work well.
2. Use GoogleStrategy 3rd parameter
Nesjts provides a way to define the number of parameters in callback function, so the callback.length will not be 0
.
We need to add callbackArity
in PassportStrategy.
export class GoogleStrategy extends PassportStrategy(Strategy, 'google', 5) {
// 5 is number of arguments in validate function
constructor() {
super({
// options
});
}
async validate(
accessToken: string,
refreshToken: string | undefined,
params: GoogleCallbackParameters,
profile: Profile,
done: VerifyCallback,
): Promise<void> {
// ...
done(null, user);
}
Upvotes: 0
Reputation: 377
Apart from the answer above by @miguel-jurado, I would like to add my two cents. In the recent version of Nest and Passport the above might lead to errors. I am using the following versions:
"@nestjs/jwt": "^10.1.0",
"@nestjs/passport": "^10.0.0",
"passport": "^0.6.0",
"passport-google-oauth20": "^2.0.0",
"passport-jwt": "^4.0.1",
and using the above code gives me Circular Dependency error. In my case, I just needed to tweak it a little to not include passReqToCallback
and so overload a different constructor. My code was:
import { PassportStrategy } from '@nestjs/passport';
import {
GoogleCallbackParameters,
Profile,
Strategy,
VerifyCallback,
} from 'passport-google-oauth20';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(configService: ConfigService) {
super(
{
clientID: configService.get('GOOGLE_CLIENT_ID'),
clientSecret: configService.get('GOOGLE_CLIENT_SECRET'),
callbackURL: configService.get('GOOGLE_CALLBACK_URL'),
scope: ['email', 'profile', 'openid'],
},
async (
accessToken: string,
refreshToken: string,
params: GoogleCallbackParameters,
profile: Profile,
done: VerifyCallback,
) => {
const {expires_in, id_token} = params;
const { name, emails, photos } = profile;
const user = {
email: emails[0].value,
firstName: name.givenName,
lastName: name.familyName,
picture: photos[0].value,
accessToken,
refreshToken,
id_token,
expires_in
};
done(null, user);
},
);
}
}
Upvotes: 0
Reputation: 29
I solved the problem by passing the callback directly in the super method as a second parameter, I do not why in the validate method it does not work, maybe it is a problem of the passportStrategy that uses nestjs.
Something like below works:
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor() {
super(
{
clientID:
process.env.BACKEND_ENV === 'dev'
? googleStrategy.GOOGLE_CLIENT_ID
: process.env.GOOGLE_CLIENT_ID,
clientSecret:
process.env.BACKEND_ENV === 'dev'
? googleStrategy.GOOGLE_CLIENT_SECRET
: process.env.GOOGLE_CLIENT_SECRET,
callbackURL:
process.env.BACKEND_ENV === 'dev'
? googleStrategy.GOOGLE_CALLBACK_URL
: process.env.GOOGLE_CALLBACK_URL,
scope: ['email', 'profile', 'openid'],
passReqToCallback: true,
},
async (
req: Request,
accessToken: string,
refreshToken: string,
params: GoogleCallbackParameters,
profile: Profile,
done: VerifyCallback,
): Promise<any> => {
const { name, emails, photos } = profile;
const user = {
email: emails[0].value,
firstName: name.givenName,
lastName: name.familyName,
picture: photos[0].value,
accessToken,
refreshToken,
};
done(null, user);
},
);
}
}
Upvotes: 2