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

2

6

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

Cardsharp answered 14/7, 2019 at 11:2 Comment(0)
V
3

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

Visualize answered 4/3, 2020 at 21:40 Comment(2)
Is this still the only way to pass and retrieve query params? Seems a lot of work just to retrieve the query params via the state.Hexaemeron
This is what currently I'm using. Works fine I didn't check any other method.Visualize
S
1

Another approach: the need was to dynamically set server url. It gets it using Context/Request.

// Custom Guard:
export const DynamicAuthGuard = (type?: string): Type<IAuthGuard> => {
    const endpoint = `auth/${type}/redirect`

    return class extends AuthGuard(type) {

        getAuthenticateOptions(context: ExecutionContext) {
            const httpContext: HttpArgumentsHost = context.switchToHttp()
            const req: Request = httpContext.getRequest<Request>()

            const serverURL = `${req.protocol}://${req.get('host')}`
            const args = 'foo=bar'
            const callbackURL = `${serverURL}/${endpoint}?${args}`
 
            return {callbackURL}
        }
    }
}

// In controller 'auth':
@UseGuards(DynamicAuthGuard('facebook')) // or any passport strategy
@Get('facebook/redirect')
async facebookRedirect(@Req() req: Request, @Res() res: Response) {
 // ...
}

Stowage answered 11/1, 2022 at 23:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.