Facebook OAuth2 does not provide user email - node.js

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.

Related

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
}

Facebook-passport strategy not returning emails field

I am using Facebook v2.5 API.
route :
app.get('/auth/facebook', passport.authenticate('facebook', { scope : 'email' }));
config :
passport.use(new FacebookStrategy({
clientID: CLIENTID,
clientSecret: CLIENTSECRET,
callbackURL: callbackURL,
profileFields: ["emails", "displayName", "gender"]
},
function (token, refreshToken, profile, done) {
console.log(profile)
...
}));
profile does not have "emails" field.
I tried passing scope as an array too
scope : ['email']
I just had the same problem: using my test account and working with the scopes, I suddenly "lost" the email address, no way to get it again using all the hints found here.
Curiously using a new facebook account, the email address appeared.
=> I went to my test account in facebook and revoked all rights for my app there. Afterwards I logged into my app again and granted all the rights needed. Since then I receive the email address again.
Hope this helps.
Make sure you define "email" scope while login, then you could get it when you ask graph.facebook.com:
$url = 'https://www.facebook.com/dialog/oauth';
$params = array(
'client_id' => $this->strategy['app_id'],
'redirect_uri' => $this->strategy['redirect_uri'],
'scope' => 'email',
);

passport-facebook in nodejs - profile fields

some of the profile fields i am trying to get from Facebook when logging in a user, are not going through.
I am using passportjs in node. this is the facebook strategy:
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: FACEBOOK_CALLBACK_URL,
profileFields: ['id', 'displayName', 'link', 'about_me', 'photos', 'email']
},
routes.handleLogin
));
being used with:
app.get('/auth/facebook', passport.authenticate('facebook', { scope: ['user_about_me', 'email'] }));
the result is that 'link', 'about_me' and 'email' are not getting pulled while the other fields are.
The profileFields parameter adheres to the Portable Contacts convention. This means you would want to use 'emails' instead of 'email'. As for the "about_me" field, it does not appear as if passport-facebook supports the OpenSocial protocol fully. This means you're out of luck if you want to use the "profileFields" parameter for both of these profile elements. The following code snippet, taken from the master branch, illustrates this limitation:
Strategy.prototype._convertProfileFields = function(profileFields) {
var map = {
'id': 'id',
'username': 'username',
'displayName': 'name',
'name': ['last_name', 'first_name', 'middle_name'],
'gender': 'gender',
'profileUrl': 'link',
'emails': 'email',
'photos': 'picture'
};
...
The fields listed in this mapping are the only ones supported right now.
Fortunately all is not lost. If you choose not to use the profileFields parameter then oddly enough you will be sent the "about_me" content you are interested in via a property called "bio". Here's how you could access that:
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: FACEBOOK_CALLBACK_URL
},
function(accessToken, refreshToken, profile, done) {
console.log("bio: " + profile._json.bio);
}));
Unfortunately this doesn't give you the other data you were interested in. I'm guessing that in your situation you are probably looking at gathering the supported convention fields during the passport-facebook callback and then grabbing the extended profile fields in a followup call using the facebook api directly. Either that or poke the passport-facebook maintainers to extend their field support.
aturkelson is correct. about_me is not supported yet. As far as email it comes with the profile as long as you request it. I also have a console log to confirm I am not crazy.
//Passport facebook strategy
exports.passportInit= passport.use(new facebookStrategy({
clientID: process.env.FACEBOOK_APP_ID ,
clientSecret: process.env.FACEBOOK_SECRET_ID,
callbackURL: '/api/auth/facebook/callback',
profileFields: ['id', 'displayName', 'emails', 'photos']
},
function(accessToken, refreshToken, profile, done) {
console.log(profile);
db.User.findOne({facebook_id: profile.id}, function(err, oldUser){
if(oldUser){
done(null,oldUser);
}else{
var newUser = new db.User({
facebook_id : profile.id,
facebook_photo : profile.photos[0].value,
email : profile.emails[0].value,
display_name : profile.displayName,
// picture: profile.picture
}).save(function(err,newUser){
if(err) throw err;
done(null, newUser);
});
}
});
}
));
According to my knowledge FB is providing you the info...
try this piece of code..
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: FACEBOOK_CALLBACK_URL,
profileFields: ['id', 'displayName', 'email'] },
function(accessToken, refreshToken, profile, done) {
// asynchronous
process.nextTick(function() {
FACEBOOK_TOKEN = accessToken;
FACEBOOK_USER = profile._json;
// facebook can return multiple emails so we'll take the first
profile.emails[0].value;
console.log(FACEBOOK_USER.email);
done(null, profile);
});
}));

Retrieving photo from Facebook using passport-facebook

I am able to retrieve basic user information via passport-facebook, following the below code and saving in mongodb:
app.get("/auth/facebook", passport.authenticate("facebook", { scope : ["email", "publish_stream", "user_location", "user_hometown", "user_birthday", "read_friendlists"]}));
app.get("/auth/facebook/callback", passport.authenticate("facebook",{ successRedirect: '/', failureRedirect: '/'}));
var mongoose = require('mongoose'),
FacebookStrategy = require('passport-facebook').Strategy,
Users = mongoose.model('Users');
module.exports = function (passport, config) {
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
Users.findOne({ _id: id }, function (err, user) {
done(err, user);
});
});
passport.use(new FacebookStrategy({
clientID: config.facebook.clientID,
clientSecret: config.facebook.clientSecret,
callbackURL: config.facebook.callbackURL
}, function(accessToken, refreshToken, profile, done) {
Users.findOrCreateFaceBookUser(profile, done);
}));};
However, I am not able to see the profile picture in the "profile".
The documentation https://github.com/jaredhanson/passport-facebook says to retrieve photos we need to pass the profileFields as below. But doing so, I am able to see the photo URL but loosing other data which were contained in _json e.g. profile._json.location.name. How can I retrieve photo with other user information intact?
passport.use(new FacebookStrategy({
// clientID, clientSecret and callbackURL
profileFields: ['id', 'displayName', 'photos', ....]},// verify callback));
If you need a larger image (default in miksii's example above is 50px x 50px which is pretty small), then use:
profileFields: ['id', 'displayName', 'name', 'gender', 'picture.type(large)']
and then
picture: profile.photos ? profile.photos[0].value : '/img/faces/unknown-user-pic.jpg'
This will return a 200px x 200px profile picture for that user.
In addition to answer of your question - you don't have to do it that way. As you mentioned you can define the required attributes for Facebook profile:
clientID: "...",
clientSecret: "...",
callbackURL: "...",
profileFields: ['id', 'displayName', 'name', 'gender', ..., 'photos']
What than you can do is just simply grab the value of the given attribute. Let's say you want to make an attribute that will hold this value:
picture: profile.photos ? profile.photos[0].value : '/img/faces/unknown-user-pic.jpg'
This proved to be a better solution since some users or sometimes the value of username may be undefined.
I hope you find this useful too,
Thank you.
Yes, the picture can be accessed via the graph api using the access token like this. "graph.facebook.com/"; + profile.username + "/picture" + "?width=200&height=200" + "&access_token=" + accessToken; There is no need to use the profile fields.
As this answer, it will be work better with this code.
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: FACEBOOK_APP_CALLBACK,
profileFields: ['id', 'displayName', 'picture.type(large)', 'email', 'birthday', 'friends', 'first_name', 'last_name', 'middle_name', 'gender', 'link']
}, (accessToken, refreshToken, profile, cb) => {
const picture = `https://graph.facebook.com/${profile.id}/picture?width=200&height=200&access_token=${accessToken}`
//
}))
2020 solution
Get users accessToken from passportjs passport-facebook strategy. Use this token to get json with a url for an users avatar:
https://graph.facebook.com/me/picture?access_token=${accessToken}&&redirect=false

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