email field is optional in passportjs facebook strategy - node.js

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.
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.
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("/");
});

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.

you need to specify email in Strategy in profileFields property
passport.use('facebook', new FacebookStrategy({
clientID: config.facebook.appId,
clientSecret: config.facebook.appSecret,
callbackURL: config.facebook.callbackURL,
profileFields: ['emails', 'first_name', 'last_name', 'locale', 'timezone']
}, function (token, refreshToken, profile, done) {
// you will get emails in profile.emails
}));

Related

Passport Github strategy not working and throwing errors

The code below is getting mad at me. I don't see anything wrong with it.
function User(profile){
console.log(profile)
}
passport.use(
new GitHubStrategy({
clientID: "my_id",
clientSecret: "secret",
callbackURL: "http://localhost:3000/auth/github/callback",
},
function(accessToken, refreshToken, profile, done) {
User(profile),function (err, user) {
return done(err, user);
};
}
)
);
app.get(
"/auth/github",
passport.authenticate("github", { scope: ["user:email"] })
);
app.get(
"/auth/github/callback",
passport.authenticate("github", { failureRedirect: "/login" }),
function(req, res) {
// Successful authentication, redirect home.
res.redirect("/");
}
);
It's throwing a big error every time I try to authenticate. Please help.
Edited the question and did as #jasonandmonte said and now I get this:
The issue I am seeing is you are defining a User function that is only logging the profile. The User function in the documentation is an example of a database model that queries a database and passes data to a callback.
To resolve the issue and replicate how passport-github's example does it, you will need to use an object modeling tool like Mongoose or Sequelize. You will then have access to something similar to User.findOrCreate() or just User.find() if you want to restrict account creation to an admin role.
To get passport working until you setup a database connection though, you should be able to update the strategy callback to invoke done.
passport.use(
new GitHubStrategy({
clientID: "my_id",
clientSecret: "secret",
callbackURL: "http://localhost:3000/auth/github/callback",
},
function(accessToken, refreshToken, profile, done) {
done(null, profile.id);
}
)
);

Passport Facebook Authentication Returns Undefined?

My facebook strategy is as follows:
passport.use(new facebookStrategy({
clientID: envVars.facebook.clientID,
clientSecret: envVars.facebook.clientSecret,
callbackURL: envVars.facebook.callbackURL,
passReqToCallback : true,
profileFields: ['id', 'emails', 'displayName']
},
function(token, refreshToken, profile, done) {
console.log(profile); //prints out undefined
}
));
And my routes are handled as follows:
router.get('/facebook', passport.authenticate('facebook'));
router.get('/auth/facebook/redirect', passport.authenticate('facebook'),
function(req, res){
res.redirect("/profile");
});
What my code manages to do successfully is direct the user to Facebook where they are prompted with a permission to allow my app to access their data. Once accepted my console.log(profile) fires yet it prints out undefined even though the user accepts the permission? I have searched through the documentation and can't seem to figure out where I went wrong.
Seems to be a problem with passReqToCallback. Refer this SO question: Using PassportJS, how does one pass additional form fields to the local authentication strategy?
In your case, remove the passReqToCallback:true flag.
passport.use(new facebookStrategy({
clientID: envVars.facebook.clientID,
clientSecret: envVars.facebook.clientSecret,
callbackURL: envVars.facebook.callbackURL,
profileFields: ['id', 'emails', 'displayName']
},
The other option is to modify your callback and use the first argument, which is the request object.
function(req, email, password, done) {
// now you can check req.body.foo
}

Rerequest does not work with passport-facebook-token

I need emails from facebook for my app to work correctly. However, the user has an option of turning off email from the auth screen. When that happens I get an empty emails field like this
"emails":[{"value":""}]
Here's my original request
passport.authenticate('facebook-token', { scope :['email'] }, function(err, profile) {
fbhandler(req, res, err, profile);
})(req, res);
and here's where I handle the returned values
passport.use(new FacebookTokenStrategy({
clientID: config.facebookAuth.clientID,
clientSecret: config.facebookAuth.clientSecret,
profileFields: ['id', 'displayName', 'emails', 'name']
},
function(token, refreshToken, profile, done) {
console.log ("##### %j", profile);
var id = profile.id,
email = profile.emails[0].value,
methodId = 2;
if (email == "") {
return done(res.json({
errors: "Facebook",
}));
}
.. Do normal stuff.
}
and the fbhandler
var fbhandler = function(req, res, err, profile) {
console.log("###### %j" ,profile);
if (!profile) {
console.log("reauth####");
passport.authenticate('facebook-token', { authType: 'rerequest', scope :['email'] }, function(err, profile) {
//Do normal stuff
})(req, res);
} else {
//do normal stuff
}
}
The rerequest never puts up the auth screen again (actually it just flashes for a second) and it returns empty emails once again.
This seems to be a passport issue and not a FB rerequest issue since I was able to do this successfully with FB JS SDK. You can view this at www.kavyavidya.us (click on fb login at the bottom, edit info in the auth screen and uncheck email address) Notice how it pops up the email address auth screen once again with no option to turn it off.
How can I ensure that the auth screen will popup again for email?

Facebook OAuth2 does not provide user email

This has been asked many times but it seems like there's no known work-around for it so I'm posting this question in the hope that someone does have a work-around for it.
I'm using NodeJS, PassportJS-Facebook.
app.get("/auth/facebook",
passport.authenticate("facebook", {
scope : [ "email" ]
}),
function (req, res) {
});
At first I thought it's a PassportJS issue but I certainly eliminated this option.
The Facebook user account I'm using clearly states:
This app needs:
Your basic info
Your email address (xyz#example.com)
Some links to this known issue (yet unsolved!):
https://developers.facebook.com/bugs/298946933534016
https://developers.facebook.com/bugs/429653750464521
https://developers.facebook.com/bugs/482815835078469
So, do you use Facebook's OAuth service? If so, do you get the user's email? How? The "straight" way? A work-around?
The Facebook strategy in passportjs, expects a profileFields field in the options. Try passing "email" in the options.
strategyOptions.profileFields = ['emails', 'first_name', 'last_name'];
Alternatively, you can override the profileUrl in the options and send:
strategyOptions.profileURL = 'https://graph.facebook.com/me?fields=location,first_name,last_name,middle_name,name,link,username,work,education,gender,timezone,locale,verified,picture,about,address,age_range,bio,birthday,cover,currency,devices,email,favorite_athletes,id,hometown,favorite_teams,inspirational_people,install_type,installed,interested_in,languages,meeting_for,name_format,political,quotes,relationship_status,religion,significant_other,sports,updated_time,website';
Facebook will ignore fields that you don't have a permission to (like email).
This should go here:
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: "http://localhost:3000/auth/facebook/callback",
profileUrl: " ..... ",
//or
profileFields: [ ... ];
},
function(accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
// To keep the example simple, the user's Facebook profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the Facebook account with a user record in your database,
// and return that user instead.
return done(null, profile);
});
}
));
```
You must provide field 'scope' in settings object:
new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: "http://localhost:3000/auth/facebook/callback",
profileUrl: " ..... ",
scope: "email",
//or
profileFields: [ ... ];
}
try to look sources.

Link facebook data to currently logged in user with Passport.js

I'm using passport.js and I'm wounding if I can link a facebook id to a logged in user's account. Something like this:
passport.use( new FacebookStrategy({
consumerKey: ---
consumerSecret: ---
callbackURL: "http://mycallback"
},
function(token, tokenSecret, profile, done) {
if (user is logged in)
user = User.addfacebookId(user, profile.id)
done(user);
}
}
));
There's a few ways to approach this, but I think one of the most straight-forward is to use the passReqToCallback option. With that enabled, req becomes the first argument to the verify callback, and from there you can check to see if req.user exists, which means the user is already logged in. At that point, you can associate the user with Facebook profile details and supply the same user instance to the done callback. If req.user does not exist, just handle it as usual.
For example:
passport.use(new FacebookStrategy({
clientID: ---
clientSecret: ---
callbackURL: "http://mycallback"
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
if (req.user)
// user is already logged in. link facebook profile to the user
done(req.user);
} else {
// not logged in. find or create the user based on facebook profile
}
}
));

Resources