Search code examples
node.jspassport.jsfacebook-loginfacebook-oauthpassport-facebook

email field is optional in passportjs facebook strategy


I wrote the code for login with facebook, everything works and I'm getting the user's email address. But there is another option on facebook which lets the user select the data my application is going to have access to.

enter image description here

If user clicks on that, he'll see the name and everything marked as required, but email is optional and the user can remove it from the data that is going to be provided to the app.

enter image description here

On the application, email is required. So how I can mark the email as required on facebook?

This is the snippet I'm using in the code.

passport.use(new FacebookStrategy({
        clientID: config.social.facebook.clientID,
        clientSecret: config.social.facebook.clientSecret,
        callbackURL: config.url+'auth/facebook/cb',
        enableProof: false,
        profileFields:['id', 'name', 'emails'],
        scope: "email"
    }, function(accessToken, refreshToken, profile, done) {
        // doing the rest of the thing
    }
));

// ...

app.get('/auth/facebook', passport.authenticate('facebook', {scope: ['email']}));
app.get('/auth/facebook/cb', passport.authenticate('facebook'), function(req, res, next) {
    res.redirect("/");
});

Solution

  • I solved the problem by re-requesting the permission.

    Turns out I can add authType: 'rerequest' to passport.authenticate('facebook', {scope: ['email'], authType: 'rerequest'}).

    What I did is to check if the emails field is present in the result, if not, I call done with an error.

    function(accessToken, refreshToken, profile, done) {
        if (profile.emails === undefined) {
            done('email-required')
            return;
        }
        // doing the rest of the thing
    }
    

    Then to catch the error, I had to write a custom callback for passport.authenticate('facebook').

    app.get('/auth/facebook/cb', function(req, res, next) {
        passport.authenticate('facebook', function (err, user, info) {
            if (err) {
                if (err == 'email-required') res.redirect('/auth/facebook/rerequest');
                // check for other kinds of errors and show proper messages
                return;
            }
            req.user = user;
            // do the rest of the thing
        })(req, res, next)
    });
    

    As you see, I redirect the user to another route /auth/facebook/rerequest in case of error.

    app.get('/auth/facebook/rerequest',
        passport.authenticate('facebook', {
            scope: ['email'],
            authType: 'rerequest' // this is important
        }
    ));
    

    This will redirect the user to the same page on FB again and this time email field is required. I couldn't do this in the same route; apparently it was using the same generated code to communicate to fb which was not acceptable by fb.

    And that's how I managed to solve the issue.