NestJS - Multi third party auth provider

238 Views Asked by At

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

0

There are 0 best solutions below