Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I have multiple facebook strategies for passport?

I want to handle the following passport-facebook related operations differently.

  1. Sign-Up with Facebook
  2. Log-In with Facebook
  3. Connect existing account to Facebook

For 'sign-up' I want to do/check:

  • If the user already exists (based on facebook oauth id), redirect to log-in.
  • If the user already exists (based on email address from facebook profile), prompt the user to sign in with email, then connect their facebook account.
  • Create a new user & sign them in.

For 'log-in' and 'connect account' I want to perform a series of other checks/operations.

I've looked at the passport documentation for facebook and the passport-facebook module and a bunch of related Stack Overflow questions, but am still struggling with how to implement this.

How can I implement different facebook strategies (via passport) with different callbackURLs and options?

like image 781
Derek Soike Avatar asked Jan 08 '23 17:01

Derek Soike


1 Answers

After a good deal of digging, this is what I ended up doing:


Create 3 separate passport strategies.

Each with different names (facebookSignUp vs facebookLogIn vs facebookConnect) and different callbackURL paths (.../sign-up/clbk vs .../log-in/clbk vs .../connect/clbk).

// facebook strategy 1 (sign-up with facebook)
passport.use('facebookSignUp', new FacebookStrategy({
        clientID: FACEBOOOK_APP_ID,
        clientSecret: FACEBOOK_APP_SECRET,
        callbackURL: 'http://website/auth/facebook/sign-up/clbk'
    },
    function(accessToken, refreshToken, profile, clbk) {
        return clbk(profile);
    }
));

// facebook strategy 2 (log-in with facebook)
...

// facebook strategy 3 (connect facebook to existing account) 
...

Create routes for each strategy.

Initial request route & callback route after facebook authentication. So 2 routes per strategy.

var member = require('../member');              // member module controller

// sign-up with facebook
app.route('/auth/facebook/sign-up')
    .get(member.facebookSignUp);                // passport redirect to facebook for authentication

// sign-up with facebook callback
app.route('/auth/facebook/sign-up/clbk')
    .get(
    member.facebookSignUpClbk,                  // parse facebook profile to create new user & check for existing account -> redirect to log-in route
    member.checkEmail,                          // check if email is already used -> throw error 'please log in with email, then connect facebook in settings'
    member.checkUrl,                            // get unique url
    member.signUp,                              // create new user & sign-in
    member.email.welcome,                       // send welcome email
    member.facebookRedirectDashboard            // redirect to member dashboard
);

// log-in with facebook
app.route('/auth/facebook/log-in')
    .get(member.facebookLogIn);                 // passport redirect to facebook for authentication

// log-in with facebook callback
app.route('/auth/facebook/log-in/clbk')
    .get(
    member.facebookLogInClbk,                   // authenticate user and log-in & check if user exists (fb oauth id or email address) -> throw error 'please sign up with facebook or log in with email'
    member.lastLogin,                           // update user's last login field
    member.facebookRedirectDashboard            // redirect to dashboard
);

// connect facebook profile to existing account
...

// connect facebook profile to existing account clbk
...

Where the actual passport authentication is performed in the following files/functions.

member.facebookSignUp just calls passport.authenticate(), which redirects to facebook.

// member.facebookSignUp
exports.facebookSignUp = function(req, res, next) {
    passport.authenticate('facebookSignUp', {          // use the 'facebookSignUp' strategy
        display: null,                                 // null = let facebook decide (or 'page' (default), 'popup', 'touch', etc)
        scope: [
            'public_profile',                          // profile returned by default, but specified here anywhere
            'email',                                   // ask for email address
            'user_location'                            // ask for location
        ]
    })(req, res);
};

member.facebookSignUpClbk gets executed after facebook authorizes the user and redirects to the callback route. It is where everything else happens.

// member.facebookSignUpClbk
exports.facebookSignUpClbk = function(req, res, next) {

    // parse profile & plug into req.body for new user creation in later fxn
    function parseProfile(profile) {
        // user doc
        req.body = {};
        // facebook profile data
        req.body.facebook = (profile._json) ? profile._json : {id: profile.id};
        // name
        ...
        // email
        ...
        next();
    }

    // check existing users (mongoose/mongodb)
    function checkUser(profile) {
        User.findOne({query}, function(err, userDoc) {
            if (err) {
                return res.redirect(
                    'http://'+req.headers.host+
                    '?header=Sign-Up Error!'+
                    '&message=We had trouble signing you up with Facebook. Please try again'
                );
            } else if (userDoc) {
                // redirect to log-in fxn
                return res.redirect('http://website/auth/facebook/log-in');
            } else {
                parseProfile(profile);
            }
        });
    }

    // passport authentication
    function passportAuth() {
        passport.authenticate('facebookSignUp', function(profile) {
            if (!profile || !profile.id) {
                return res.redirect(
                    'http://'+req.headers.host+
                    '?header=Sign-Up Error!'+
                    '&message=We had trouble signing you up with Facebook. Please try again or sign-up via email.'
                );
            } else {
                checkUser(profile);
            }
        })(req, res);
    }

    // start process
    passportAuth();
};

member.facebookLogIn, .facebookLogInClbk, .facebookConnect, & .facebookConnectClbk are set up in a similar way, just different logic in the 'Clbk' functions depending on what I'm trying to do.


Hope this helps!

like image 200
Derek Soike Avatar answered Jan 28 '23 03:01

Derek Soike