How to get id_token from passport-google-oauth20 package - NestJs - nestjs

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.

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

Related

Discord api join guild, unauthorized 401 error

I'm trying to authenticate a user with Discord oauth2, then add this user to the guild. I'm also using Passportjs to authenticate the user, so the DiscordStrategy follows as
#Injectable()
export class DiscordStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
clientID: process.env.DISCORD_CLIENT_ID,
clientSecret: process.env.DISCORD_CLIENT_SECRET,
callbackURL: `http://${process.env.HOST}:${process.env.PORT}/auth/discord/callback`,
scope: ['identify', 'guilds', 'guilds.join'],
});
}
async validate(accessToken: string, refreshToken: string, profile: Profile) {
const { id } = profile;
console.log(profile);
const resp = await this.authService.joinGuild(accessToken, id);
console.log(resp);
}
}
and the authService.joinGuild
async joinGuild(accessToken: string, userId: string) {
return this.httpService
.put(
`https://discord.com/api/v8/guilds/${process.env.DISCORD_GUILD_ID}/members/${userId}`,
{
headers: {
Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN}`,
},
body: {
access_token: `${accessToken}`,
},
},
)
.pipe(
catchError((e) => {
throw new HttpException(e.response.data, e.response.status);
}),
)
.pipe(
map((res) => {
console.log(res.data);
return res.data;
}),
)
.toPromise();
}
and my response data is data: { message: '401: Unauthorized', code: 0 }
What am I doing wrong here? I tried to give my bot every permission possible as well. Thanks.

Req.user is available only under a specific route - NestJS

I use NestJS with passport for my authentication via Google. Unfortunately, I have a problem with req.user as I can get it only under the callback route, but not anywhere else.
So the question is: How to add req.user to all other routes within the module?
To demonstrate it better: In my Google.strategy.ts I handle the req and all - and of course I have the access to the google's profile.
#Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(private readonly authService: AuthService) {
super({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_SECRET_KEY,
callbackURL: '/api/v1/auth/google/callback',
scope: ['email', 'profile'],
});
}
async validate(accessToken, refreshToken, profile, done) {
const user = await this.authService.verifyUser(profile.id);
if (user) {
done(null, user);
console.log('user already exists');
} else {
const user = this.authService.save({
googleId: profile.id,
displayName: profile.displayName,
});
done(null, user);
console.log('user created');
}
}
}
Here lays the problem though, after the validation of the request I can access req.user ONLY in the callback route.
#Controller('/api/v1/auth/google/')
export class AuthController {
constructor(private readonly authService: AuthService, private readonly googleStrategy: GoogleStrategy) {
}
#Get('')
#UseGuards(AuthGuard('google'))
// eslint-disable-next-line #typescript-eslint/no-empty-function
async googleAuth(#Req() req) {}
#Get('callback')
#UseGuards(AuthGuard('google'))
async googleAuthRedirect(#Res() res, #Req() req) {
//HERE IT WORKS AND I CAN ACCESS REQ.USER WITH ALL THE DETAILS
console.log(req.user)
}
#Get('login')
async info(#Req() req, #Res() res) {
//HERE I GET 'UNDEFINED'
console.log(req.user)
}
}
I found this problem today when I was actually trying to grab the data from the callback route, but... I cannot!
I always get:
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2...' (redirected from 'http://localhost:4000/api/v1/auth/google/callback') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I tried to enableCors, add options, headers... literally everything, but I'm still getting this error, so I wanted to use a different route with the req.user (and with plain express.js of course it worked). Any ideas, guys?

Passport login not calling the function in strategy file

I am trying to use Passport for SSO. My problem is that when I log in with any of the options everything is fine, except the data saving... I think the functions in the strategy files are not called (the log is not working neither).
For example the Google strategy:
#Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(private userService: UserService) {
super({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: 'http://localhost:4200',
scope: ['email', 'profile'],
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: any,
done: VerifyCallback,
): Promise<any> {
try {
console.log(profile);
const user = profile;
this.userService.FindOrCreate(profile);
done(null, user);
} catch (err) {
done(err, null);
}
}
}
Controller:
#Get('google')
#UseGuards(AuthGuard('google'))
async twitterauth(#Req() req) {
return await this.authService.login(req.user);
}
AuthService:
#Injectable()
export class AuthService {
private readonly logger = new Logger(AuthService.name);
constructor(
private userService: UserService,
private readonly jwtService: JwtService,
) {}
async validateUser(email: string, password: string): Promise<User> {
const user: User = await this.userService.findOne({
where: { email },
});
if (!user) {
return null;
} else {
if (await bcrypt.compare(password, user.password)) {
return user;
} else {
this.logger.error('Password is incorrect.');
return null;
}
}
}
async login(user: any) {
const payload = { email: user.email, role: user.role };
return {
// eslint-disable-next-line #typescript-eslint/camelcase
access_token: this.jwtService.sign(payload),
};
}
}
The other strategies (fb, linkedin, instagram, github) are quite the same and the problem is the same.
The problem, as found in chat, was that the callback that Google was calling to in the OAuth flow, was not a part of the same server, and as such, the NestJS server could not react to the incoming data, hence why the validate was never called.
That callback route needs to point to your NestJS server so that it can handle the saving logic for the database,OR the angular applications needs to re-route the return to it back to the NestJS server. Either way, your validations aren't being called because your Nest server never gets the callback with all the sensitive information from Google
More than likely, it will be better to have the callback pointed at your server so that the data is formatted as Passport is expected.

Login by Passport Facebook by nestJs framework

I'm trying to build a login by Facebook through API endpoint ( My first time ) for mainly a mobile APP using NestJs framework and passport lib
I have followed this article here but I don't know what next?! also when accessing the endpoint just says not authorized!
I need to register a user if not exist and login if it exists
My code so far ( FB strategy )
import { Injectable } from "#nestjs/common";
import { use } from "passport";
import { UsersService } from "../routes/users/users.service";
import PassportFacebookToken = require("passport-facebook-token");
#Injectable()
export class FacebookStrategy {
constructor(
private readonly userService: UsersService,
) {
this.init();
}
init() {
use(
new PassportFacebookToken(
{
clientID: '',
clientSecret: '',
fbGraphVersion: 'v3.0',
},
async (
accessToken: string,
refreshToken: string,
profile: any,
done: any,
) => {
const user = await this.userService.create(
{
username: profile.displayName,
email: profile.emails[0].value,
picture: profile.photos[0].value,
},
);
return done(null, user);
},
),
);
}
}
Service:
async create(
user: Partial<UserDTO>
): Promise<UserDTO> {
let userExist: UserDTO = await this.userRepository.findOne({ where: { username: user.username } });
if (userExist) {
throw new HttpException('User already exists', HttpStatus.BAD_REQUEST);
}
let createdUser = this.userRepository.create(user);
return (await this.userRepository.save(createdUser));
}
Controller
#UseGuards(AuthGuard('facebook-token'))
#Get('facebook')
async getTokenAfterFacebookSignIn(
#Req() req: any
) {
// return this
}
Your client will need to transmit the access_token that is received from Facebook after login, send the access_token as a query param to your Facebook auth endpoint.
GET /auth/facebook?access_token=<TOKEN_HERE>
You should check here for clarification
In case someone is new here or still following this question, There is an awesome npm package which provides various social login implementation in your NestJS application. You can login with google, facebook, twitter and many more.
https://github.com/mjangir/nestjs-hybrid-auth

Passport JwtStrategy never executed in nestjs

I am using nestjs and having an issue with using guards to authenticate a request.
My JwtStrategy is never execute.
Here is my JwtStrategy :
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(
#Inject('IQueryBusAdapter')
private readonly queryBus: IQueryBusAdapter,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET_KEY,
});
}
validate = async (payload: IJwtPayload) => {
Logger.log('INNNNN');
const query = new GetUserByIdQuery();
query.id = payload.id;
const user = await this.queryBus.execute(query);
if (!(user instanceof User)) {
throw new UnauthorizedException();
}
return user;
};
}
I also try to call directly validate() directly in constructor but nothing change...
My AuthModule :
#Module({
imports: [
BusModule,
JwtModule.register({
secretOrPrivateKey: process.env.JWT_SECRET_KEY,
signOptions: {
expiresIn: process.env.JWT_EXPIRES,
},
}),
PassportModule.register({ defaultStrategy: 'jwt' }),
TypeOrmModule.forFeature([User]),
],
controllers: [RegisterAction, LoginAction],
providers: [
JwtStrategy,
],
})
export class AuthModule {}
My controller
#Get('/:id')
#ApiOperation({ title: 'Get user ressource' })
#UseGuards(AuthGuard('jwt'))
async index(#Param() query: GetUserByIdQuery): Promise<object> {
const user = await this.queryBus.execute(query);
if (!(user instanceof User)) {
throw new NotFoundException();
}
return {
id: user.id,
fullName: user.getFullName(),
email: user.email
};
}
I always received 401 status code.
Thanks for your help.
validate will only be called when you pass a valid jwt token. When the token is signed with a different secret or is expired, validate will never be called. Make sure you have a valid token. You can check your token with the jwt debugger.

Resources