I'm trying implementing multi authentification solutions with Google and Twitch and without local strategy so I don't store any personal credentials.
The things is that I would like to link both accounts if user want to (to have access to more functionnality)
So I started to create strategy with passport for twitch, google and jwt
// google.strategy.ts
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(
private userService: UsersService,
private configService: ConfigService,
) {
super({
clientID: configService.get<string>('GOOGLE_ID'),
clientSecret: configService.get<string>('GOOGLE_SECRET'),
callbackURL: `${configService.get<string>(
'BASE_URL_APP',
)}/auth/google/callback`,
scope: ['email', 'profile'],
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: any,
cb: Function,
): Promise<any> {
let user = await this.userService.findOneByGoogleProviderId(profile.id);
if (!user) {
user = await this.userService.create({
creatorsFollowed: [],
providerAccess: [
{
accessToken: accessToken,
platform: Platform.GOOGLE,
providerUserId: profile.id,
refreshToken: refreshToken,
email: profile['_json']['email'],
},
],
firstname: profile['_json']['given_name'],
lastName: profile['_json']['family_name'],
});
}
return cb(null, user);
}
}
Then in my auth controller
@Get('google')
@UseGuards(AuthGuard('google'))
async googleAuth() {
}
@Get('google/callback')
@UseGuards(AuthGuard('google'))
async googleAuthCallback(@Req() req) {
const providerPurgedData = req.user.providerAccess.map(
({ accessToken, refreshToken, ...keepAttrs }) => keepAttrs,
);
const payload = {
providersData: providerPurgedData,
firstname: req.user?.firstname,
lastname: req.user?.lastname,
};
return { accessToken: this.jwtService.sign(payload) };
}
Finally I would like to reuse these endpoint and verify if the user is already authenticated (client send jwt received when he logged in with twitch for example) If I found a correct JWT I don't generate a new one but simply link the google account with twitch account in my db
I tried to add a middleware for both route google/callback and twitch/callback but I cant find how to verify that there is a jwt token in it and I dont know if there is maybe a better approach than middleware, I though about overiding guard too
// middleware
import { Injectable, NestMiddleware } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request, Response } from 'express';
@Injectable()
export class LinkProviderAccountMiddleware implements NestMiddleware {
constructor(private jwtService: JwtService) {}
use(req: Request, res: Response, next: () => void) {
const bearer = req.headers['authorization'].split(' ')[1];
if (this.jwtService.verify(bearer)) {
// link account
res.status(200).json({ msg: 'account linked' });
}
next();
}
}
Edit - 25/06/2023
Finaly I managed to do what I want using a middleware, I also updated my twitch and google strategy to be able to retrieve jwt sent by the first request
//middleware
import {
Injectable,
NestMiddleware,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request, Response } from 'express';
@Injectable()
export class CheckUserLoggedInMiddleware implements NestMiddleware {
constructor(private jwtService: JwtService) {}
use(req: Request, res: Response, next: () => void) {
const token = req.headers.authorization?.split(' ')[1];
if (token) {
try {
const decoded = this.jwtService.verify(token);
req.user = decoded;
return next();
} catch (error) {
throw new UnauthorizedException();
}
}
next();
}
}
// google.strategy
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(
private userService: UsersService,
private configService: ConfigService,
) {
super({
clientID: configService.get<string>('GOOGLE_ID'),
clientSecret: configService.get<string>('GOOGLE_SECRET'),
callbackURL: `${configService.get<string>(
'BASE_URL_APP',
)}/auth/google/callback`,
scope: ['email', 'profile'],
passReqToCallback: true, <= ADDED THIS ---------
});
}
So now my strategies have user in request headers if an user call twitch or google auth endpoint with a valid JWT token