I am trying to use passportjs to login using facebook services. There are a lot of examples online, but none that really explicitly illustrate an implementation with no persistent sessions as I plan to use JWTs to identify my users.
I have tried to this on my own but I keep getting an error response Cannot GET /auth/facebook/callback. See my implementation below.
Routes file
router.get('/facebook', passport.authenticate('facebook',{ scope : ['email'] }));
router.get('/facebook/callback',
passport.authenticate('facebook', {
session: false
}));
Passportjs
passport.use(new FacebookStrategy({
clientID: config.facebookAppId,
clientSecret: config.facebookAppSecret,
callbackURL: config.facebookCallbackURL,
profileFields: ['email']
},
function(accessToken, refreshToken, profile, done) {
//find the user based off of the facebook profile id
User.findOne({oauthID: profile.id}).exec()
//find the user or create a new record
.then(function(user){
if(user) return done(null, user);
var user = new User({
oauthID: profile.id,
oauthProvider: 'facebook',
email: profile.emails[0].value,
name: profile.displayName
});
user.save();
})
//send back error if encountered
.catch(done);
}
));
Related
I looked in several posts, but I cannot find something that meets my situation.
To login (or signup) with google, you have to go to mydomain.com/login/google
You then, well, login with google, and then the callback is handled on mydomain.com/auth/google
Here is the code responsible for this.
app.get('/login/google', passport.authenticate('google'));
app.get('/auth/google',
passport.authenticate('google', { failureRedirect: '/login', failureMessage: false, session: false, failureFlash: true }),
function(req, res) {
res.redirect('/');
});
Here is where I store the users:
passport.use(new GoogleStrategy({
clientID: no,
clientSecret: definitely not,
callbackURL: 'https://no cuz privacy/auth/google'
},
async function(issuer, profile, cb) {
var user = await User.find({ google: true, googleid: profile.id })
if (!user[0]) {
// The Google account has not logged in to this app before. Create a
// new user record and link it to the Google account.
const newUser = await new User({ username: generateUsername(), google: true, googleid: profile.id, googleProfile: profile })
await newUser.save()
return cb(null, newUser);
} else {
// The Google account has previously logged in to the app. Get the
// user record linked to the Google account and log the user in.
console.log('exists')
return cb(null, newUser);
}
}
));
I think you have to do something with the callback function (cb()) to somehow go to app.get('/auth/google') for the redirect, but, all it does is print either exists or new in the console, spinning forever on the client side. Not sure how to redirect after the code determines either account exists or new account.
Edit: I just want to point out that the cb() function could also be done() too. For example:
function(accessToken, refreshToken, profile, done){
console.log("strategy");
console.log(profile);
console.log(accessToken);
console.log(refreshToken);
done(null, profile);
}
^^ Not my code --> PassportJS in Nodejs never call the callback function ^^
Unable to retrieve the user email on LinkedIn. I have used passport-LinkedIn and OAuth 2.0. I can interpolate the username and picture. This is the code that I have tried.
var LinkedIn = require('passport-linkedin-oauth2').Strategy;
module.exports = (passport, User) => {
passport.use( new LinkedIn({
clientID: '86ew637ipvirsa',
clientSecret: 'HoEMfqCBGL9SxsIt',
callbackURL: 'http://localhost:3000/auth/linkedin/callback'
}, (accesstoken, refreshToken, profile, done) => {
User.findOne({'linkedin.id': profile.id}, (err, x) => {
if (x) return done(null, x);
var user = {
displayName: profile.displayName,
image: profile._json.pictureUrl,
email: profile.emailAddress,
linkedin: {
id: profile.id
}
};
User.create(user);
User.create(user, (err, x) => done(null, x));
});
}));
};
the npm package being used by you is not properly documented. The author has not explicitly said how you can access the email field from the profile variable.
You can pass in the scope with strategy and get the email fields by logging the profile variable.
passport.use(new LinkedInStrategy({
clientID: LINKEDIN_KEY,
clientSecret: LINKEDIN_SECRET,
callbackURL: "http://127.0.0.1:3000/auth/linkedin/callback",
scope: ['r_emailaddress', 'r_basicprofile'], //pass the scope
state: true
}, function(accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
console.log(profile); //logging
return done(null, profile);
});
}));
You can also use another package . Here you can explicitly define the profile fields you want to access.
currently I am working on a project to store information from Facebook Authentication, I already checked Passport-facebook doesn't get email to see the solution, and here is my code
passport.use(new FacebookStrategy(
{
clientID: 'xxxxxxxxxxxxx',
clientSecret: 'xxxxxxxxxxxxxxxx',
callbackURL: 'http://1bc600b4.ngrok.io/auth/facebook/callback',
profileFields: ['id', 'displayName', 'name', 'photos', 'email'],
},
(accessToken, refreshToken, profile, done) => {
console.log(profile);
models.User.findOne({ where: { facebookID: profile.id } }).then((existinguser) => {
if (existinguser) {
// Nothing will happen, the ID already exists
done(null, existinguser);
} else {
models.User.create({
// email: profile.emails[0].value,
username: 'hello',
firstname: profile.name.givenName,
lastname: profile.name.familyName,
facebookID: profile.id,
avatar: `https://graph.facebook.com/${profile.id}/picture?redirect=true&height=470&width=470`,
avatarThumb: profile.photos[0].value,
last_login: Date.now(), }).then(user => done(null, user));
}
});
},
));
app.use(passport.initialize());
app.get('/flogin', passport.authenticate(
'facebook',
passport.authenticate('facebook', { scope: ['profile', 'email'] }),
));
app.get(
'/auth/facebook/callback',
passport.authenticate('facebook', { session: false }),
(req, res) => {
res.send('AUTH WAS GOOD!');
},
);
I don't understand why I use this way and the email information still not show up, which makes me lose a lot of information, can anyone give me a hint on solving this prob? Thank you!
This problem has been there for a while, you need to include email scope and even then it might not send facebook email because of user privacy settings or if the user's email is not verified at the facebook's end.
You could add scope like this:
passport.use(
new FacebookStrategy({
clientID: 'CLIENT_ID',
clientSecret: 'CLIENT_SECRET',
callbackURL: "http://www.example.com/auth/facebook/callback"
passReqToCallback : true,
profileFields: ['emails'] // email should be in the scope.
})
)
Also you would need to add this to your authorize route as well,
app.get('/login/facebook', passport.authorize('facebook', { scope : ['email'] }));
If the emails return, It will be an list of it and you could access them like this:
1. profile.emails // [email1#example.com, somerandomdude#gmail.com]
2. profile.emails[1].value // randomdude#yahoo.com
If it doesn't return, you can use their username or id and create a facebook email atleast temporarily like this:
username#facebook.com // this exists.
How do I get the message and display it in my router.post('/auth')?
passport.use(new FacebookTokenStrategy({
clientID: 'HIDDEN',
clientSecret: 'HIDDEN'
}, function(accessToken, refreshToken, profile, done) {
console.log(profile);
var user = {id: profile.id, first_name: profile.name[1], last_name: profile.name[0], email: profile.emails[0], profile_picture: profile.photos[0]};
var error = null;
return done(error, user, {message: "HOW TO RETRIEVE THIS MESSAGE!"});
}
));
I've tried to retrieve this message by saying console.log(req.message) or console.log(req.session.message), I just don't know how to get it. I've also tried console.log(req.session.passport.message)
router.post('/auth', passport.authenticate('facebook-token'), function(req, res){
console.log("Verifying");
console.log("HOW TO LOG THAT MESSAGE HERE?");
if(req.isAuthenticated()){
console.log(req.session.passport.user);
}else{
console.log("NOT");
}
});
I don't think that the third argument is passed in any way if the authentication was successful (which in your case it always is; by default, Passport will return a 401 when authentication was unsuccessful, and your handler wouldn't get called at all).
However, you can add extra properties to req if you configure the strategy to pass it to the verification callback:
passport.use(new FacebookTokenStrategy({
clientID: 'HIDDEN',
clientSecret: 'HIDDEN',
passReqToCallback : true,
}, function(req, accessToken, refreshToken, profile, done) {
req.message = 'Your Message Here';
...
}
}))
And then in your handler, you can access it as req.message as well.
I am trying to establish a login system for my app using passport-facebook.
everything goes well except for the 2 fields that are getting undefined back from the request.
I will post my entire code for the login procedure, since I haven't seen a lot of info about it here even though there are a lot of question in the matter.
this is the configuration in app.js
var passport = require('passport');
var FacebookStrategy = require('passport-facebook').Strategy;
passport.serializeUser(function(user, done) {
done(null, user.facebookId);
});
passport.deserializeUser(function(id, done) {
routes.findUserById(id, function(err, user) {
done(err, user);
});
});
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
));
using passport initialize and session
app.use(passport.initialize());
app.use(passport.session());
actual request handling, notice I am using the correct scope
app.get('/auth/facebook', passport.authenticate('facebook', { scope: ['user_about_me', 'email'] }));
app.get('/auth/facebook/callback', passport.authenticate('facebook', { successRedirect: '/', failureRedirect: '/error' }));
and this is my user creation function in the router
exports.handleLogin = function(accessToken, refreshToken, profile, done) {
db.userCatalog.findOne({ facebookId: profile.id }, function(err, existingUser) {
if (err) {
return done(err);
}
else {
if(existingUser) {
console.log('User: ' + existingUser.name + ' found and logged in!');
done(null, existingUser);
}
else {
new db.userCatalog({
name: profile.displayName,
facebookId: profile.id,
link: profile.link,
picture: profile.photos[0].value,
bio: profile.about_me,
email: profile.email
}).save(function (err, data) {
if (err) {
return done(err);
}
else {
console.log('New user: ' + data + ' created and logged in!');
done(null, data);
}
});
}
}
});
};
and the result when creating a new user after finishing the login procedure:
I am sure this is some rookie mistake, but I just can't figure it out myself...
Facebook returns some of the default attributes. If you want to access more details about client's profile you would have to declare it under the FacebookStrategy:
passport.use(new FacebookStrategy({
clientID: "...",
clientSecret: "...",
callbackURL: "...",
profileFields: ['id', '...', '...', 'photos', 'emails']
}, ...
So once you declare the attributes you would like to receive from Facebook, when someone try to log into your system he will be asked to share his photos or emails with you/your app. Once he approve this you can access its values:
profile.photos[0].value,
profile.emails[0].value,
...
For emails, sometimes it is useful to change:
passport.authenticate('facebook');
To this:
passport.authenticate('facebook', { scope: 'email'}));
In the profileFields you shoul use emails (plular) instead of email (singular):
profileFields: ['id', 'displayName', 'link', 'about_me', 'photos', 'emails']
This is noted in the facebook-passport documentation README.md:
profileFields parameter which specifies a list of fields (named by Portable Contacts convention)
And you can find Portable Contacts conventions for passportjs here