Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I pass dynamic param in nestjs facebook strategy callback url

How do i pass some dynamic params in the facebook login callback url?

I have different types of users (differentiated by a 'type' param) signing up using facebook login. I have created a facebook auth strategy using passport-facebook which works fine.

However after authentication, when callback url is called, i need to know which type of user requested the signup.

I'm guessing i can pass a param when defining the callback url

something like this

http://localhost:3000/auth/facebook/callback/type1 http://localhost:3000/auth/facebook/callback/type2

How do I pass a dynamic value into the FacebookStrategy??

or whats the possible workaround to achieve this?

// PassportStrategy.ts

@Injectable()
export class FacebookStrategy extends PassportStrategy(Strategy) {
    constructor() {
        super({
            clientID: 'MYID',
            clientSecret: 'MYSCRET',
            callbackURL: "http://localhost:3000/auth/facebook/callback",
            profileFields: ['id', 'displayName', 'emails', 'photos']
        });
    }

    async validate(accessToken: any, refreshToken: any, profile: any) {
        return {
            name: profile.displayName,
            email: profile.emails[0].value,
            provider: "facebook",
            providerId: profile.id,
            photo: profile.photos[0].value
        }
    }
}

// auth controller

@Controller('auth')
export class AuthController {
    constructor(
        @Inject(forwardRef(() => AuthService)) private readonly authService: AuthService,
    ) { }

    @Get('/facebook')
    @UseGuards(AuthGuard('facebook'))
    async facebookAuth(@Request() req) {
        return
    }

    @UseGuards(AuthGuard('facebook'))
    @Get('/facebook/callback')
    async facebookCallback(@Request() req) {
        return this.authService.login(req.user);
    }

}

Basically i want to be able to call "/auth/facebook/:type" and pass the type value in the callback url defined in the Strategy

and callback endpoint to be something like "/auth/facebook/callback/:type"

so when i call the authservice.login function i can pass that 'type' and decide which type of user to be created if its the first time signup

Guide me if my approach is wrong. Thanks

like image 370
mushfau Avatar asked Jul 14 '19 11:07

mushfau


1 Answers

I have been dealing recently with a similar issue here is my approach. Probably is not the best but works for now.

import { Inject, Injectable, Logger } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import passport = require('passport');
import { Strategy } from 'passport-facebook';

@Injectable()
export class FacebookStrategy extends PassportStrategy(Strategy, 'facebook') {
  private readonly logger = new Logger(FacebookStrategy.name);

  constructor(
    @Inject('FACEBOOK_STRATEGY_CONFIG')
    private readonly facebookStrategyConfig,
  ) {
    super(
      facebookStrategyConfig,
      async (
        request: any,
        accessToken: string,
        refreshToken: string,
        profile: any,
        done,
      ) => {
        this.logger.log(profile);

        // take the state from the request query params
        const { state } = request.query;
        this.logger.log(state);

        // register user

        // return callback
        return done(null, profile);
      },
    );
    passport.use(this);
  }
}
import { Controller, Get, HttpStatus, Inject, Param, Query, Req } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Redirect } from '@nestjsplus/redirect';

@Controller('auth')
export class AuthController {
    @Inject('ConfigService')
    private readonly configService: ConfigService;

    @Get(':provider/callback')
    @Redirect()
    async socialCallback(@Req() req, @Param('provider') provider: string, @Query('state') state: string) {
        // here you can use the provider and the state
        return {
            statusCode: HttpStatus.FOUND,
            url: `${this.configService.get('FRONTEND_HOST')}/dashboard`,
        };
    }
}
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AuthController } from './auth.controller';
import { FacebookStrategy } from './facebook.strategy';
import passport = require('passport');

const facebookStrategyConfigFactory = {
    provide: 'FACEBOOK_STRATEGY_CONFIG',
    useFactory: (configService: ConfigService) => {
        return {
            clientID: `${configService.get('FACEBOOK_CLIENT_ID')}`,
            clientSecret: `${configService.get('FACEBOOK_CLIENT_SECRET')}`,
            callbackURL: `${configService.get('FACEBOOK_OAUTH_REDIRECT_URI')}/callback`,
            profileFields: ['id', 'displayName', 'link', 'photos', 'emails', 'name'],
            passReqToCallback: true,
        };
    },
    inject: [ConfigService],
};

@Module({
    controllers: [AuthController],
    providers: [facebookStrategyConfigFactory, FacebookStrategy],
})
export class AuthModule implements NestModule {
    public configure(consumer: MiddlewareConsumer) {

        const facebookLoginOptions = {
            session: false,
            scope: ['email'],
            state: null,
        };
        consumer
            .apply((req: any, res: any, next: () => void) => {
                const {
                    query: { state },
                } = req;
                facebookLoginOptions.state = state;
                next();
            }, passport.authenticate('facebook', facebookLoginOptions))
            .forRoutes('auth/facebook/*');
    }
}

Now let me explain a little bit :D. The trick is in the middleware configuration.

const facebookLoginOptions = {
    session: false,
    scope: ['email'],
    state: null,
};
consumer
    .apply((req: any, res: any, next: () => void) => {
        const {
            query: { state },
        } = req;
        facebookLoginOptions.state = state;
        next();
    }, passport.authenticate('facebook', facebookLoginOptions))
    .forRoutes('auth/facebook/*');

So, oAuth has this feature that you can pass a state param through the login flow. By extracting the passport option in a variable we can change the state param dynamically by applying another middleware before the passport one. In this way, you can call now http://localhost:3000/auth/facebook/login?state=anything-you-want and this state query param will be passed through the strategy and also in the callback call.

I have also created a git repo with the example: https://github.com/lupu60/passport-dynamic-state

like image 183
Bogdan Avatar answered Dec 02 '22 20:12

Bogdan