Passport Facebook Authentication Returns Undefined? - node.js

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
}

Related

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

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

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