I tried to implement facebook OAUTH for my applications(Web and Mobile) using Sails.js/Node.js and Mongo as my backend. I wrote a dedicated function to access the endpoints for each app cause the webApp needs a callback URL and the mobile doesn't. My code screenshot is available below. But, I noticed the two gives me two different Facebook id. I am confused, shouldn't the facebookId be unique? I am using these two modules
FacebookStrategy = require('passport-facebook').Strategy
FacebookTokenStrategy = require('passport-facebook-token').Strategy
WebApp Strategy
passport.use(new FacebookStrategy({
clientID : facebook.clientID,
clientSecret : facebook.clientSecret,
profileFields : ['id', 'email', 'last_name', 'first_name', 'gender', 'link', 'verified', 'updated_time', 'timezone', 'age_range'],
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
authenticate(req, accessToken, refreshToken, profile, done);
}
));
The authenticate function being pointed at
var authenticate = function (accessToken, refreshToken, profile, done) {
User.findOne({facebookId: profile.id}).then(function (user){
if (user) {
return [user, {accessToken: accessToken, refreshToken: refreshToken}]
} else {
var data = {
facebookId: profile.id,
firstname: profile._json.first_name,
lastname: profile._json.last_name,
email: profile._json.email,
gender: profile._json.gender,
fb_profile_url: profile._json.link,
fb_verified: profile._json.verified,
fb_updated_time: profile._json.updated_time,
phonenumber: profile._json.phone
}
var userQry = User.create(data);
}
return [userQry, {accessToken: accessToken, refreshToken: refreshToken}]
})
.spread(function (user, credentials){
if (user) {
return done(null, [user, credentials]);
} else {
return done(null, null);
}
})
.catch(function (err){
return done(err, null);
});
}
Related
I am working on a MERN stack appliaction and I wanna integrate google authencation using PassportJs, I already configured the server-side and ran a couple of test which were successful but all of a sudden it no longers returns the usual profile object which contains the user's name, first_name, _json e.t.c, instead it sends back this object;
{
access_token: 'ya29.A0ARrdaM8QpjEP_3sDyDRBT8OJiOlXVgOHFcyEV1nne13jd_qRelaTYh5ry0H0E8WvmOs14h6PycgTHqteS85U9lPxj2sfhnabOI6XdMgWAM_Z_y4WR1F50NR7MVDcjpH6aS8xLzewScSt8R-6Cs6t4Adn3Vgu',
expires_in: 3599,
scope: 'https://www.googleapis.com/auth/userinfo.email openid https://www.googleapis.com/auth/userinfo.profile',
token_type: 'Bearer',
id_token: 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImNlYzEzZGViZjRiOTY0Nzk2ODM3MzYyMDUwODI0NjZjMTQ3OTdiZDAiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI5NDc2NDU0NDY5NTctZWhiNXN1dWduZnZkcGJzNnE5cXZjNDQ2ZWRzZTY4ZWwuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI5NDc2NDU0NDY5NTctZWhiNXN1dWduZnZkcGJzNnE5cXZjNDQ2ZWRzZTY4ZWwuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMDA3NDM5NTc3MzIwMDkwMzE5OTAiLCJlbWFpbCI6InNpbW9uY2NvdmVuYW50QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiODBfN20xMVJVNHV5SHZiblc2UEZIZyIsIm5hbWUiOiJTaW1vbiBDb3ZlbmFudCIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQVRYQUp4QWtNdEhtSWFjLV9CNEFzVkVYQU5jMUF1WEh5QU5KNTdFVVRVPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IlNpbW9uIiwiZmFtaWx5X25hbWUiOiJDb3ZlbmFudCIsImxvY2FsZSI6ImVuIiwiaWF0IjoxNjQ5MTA5MDk5LCJleHAiOjE2NDkxMTI2OTl9.cRn6YA8OYSkp_MGLoDQTqeu6R5Ajm3KQMg6cQkZaBXuduJIJOnVL1r-lgCAIDHmkTsH-gohBIWcELPL0Pzm0rueW2X6b0LEzPdFNMsHFL_RkbRh2bwPwqAZqToaEJsN6M9DqqQwjuc8aENwHOxflVfTqM71aidt96cEIucRcCYxF1Q-rxBw4STNy0c2Lqae_85fFO5uArEJPyOPYDjVYjEqR0wNWFezRadA8zAKV7tv2WJFhEbA2tgnnbIKP5rWmkF6V6mlbFKv9p2qFvBLUpj6ffqVnQZmwILJng6GvNrWu03VfbAvHao4PA-qLwPnge65hqjet3S8TxzlNkkAtDA'
}
This my passport setup:
const strategy = new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "/auth/google/callback",
},
function (accessToken, refreshToken, profile, cb, done) {
console.log(profile);
const { email, given_name, family_name, email_verified, gender } = profile._json;
User.findOne({ email }).exec((err, user) => {
if (user) {
const token = jwt.sign({ _id: user._id }, process.env.JWT_SECRET, {
expiresIn: "7d",
});
const { password, ...others } = user._doc;
return cb(err, others, token);
} else {
let password = email + process.env.JWT_SECRET;
user = new User({
firstname: given_name,
lastname: family_name,
email,
password: CryptoJS.AES.encrypt(
password,
process.env.PASS_SEC
).toString(),
gender: "male",
});
console.log(user);
user.save((err, data) => {
console.log(data);
if (err) {
console.log("Save error", errorHandler(err));
return done(null, false, { message: errorHandler(err) });
} else {
const token = jwt.sign({ _id: data._id }, process.env.JWT_SECRET, {
expiresIn: "7d",
});
const { password, ...others } = data._doc;
console.log(others);
return cb(err, token, others);
}
});
}
});
}
);
passport.use(strategy);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
And this is my routes :
router.get(
"/google",
passport.authenticate("google", {
scope: ["profile", "email"],
})
);
router.get(
"/google/callback",
passport.authenticate("google", {
successRedirect: "/login/success",
failureRedirect: "/login/failed",
})
);
I thought maybe it had something to do with the accessToken so I tried using the "passport-oauth2-refresh" dependency to refresh and get a new accessToken;
const refresh = require("passport-oauth2-refresh");
refresh.use(strategy);
But that didnt work, I have tried searching stack-overflow for similar issues but found none. The strange thing was that it was working before, cant seem to figure out what went wrong.
I also wanna ask, after successful authentication I want to be able to send the user to my frontend and store it in localStorage but I found out that Google or Oauth don't support CORS. So I cannot make axios request nor catch server response, so what would be the optimal way to handle the authentication on the frontend?
My frontend google login code:
const google = () => {
window.open(`${process.env.REACT_APP_API_URL}/auth/google`, "_self");
}
I am trying to sign up my users using facebook using passportjs. I am using mongodb as database and using mongoose to interact with it.
const userSchema = mongoose.Schema({
email: String,
password: String,
googleId: String,
facebookId: String,
secret: [String],
});
const User = mongoose.model("users", userSchema);
This is the schema.
passport.use(new FacebookStrategy({
clientID: process.env.APP_ID,
clientSecret: process.env.APP_SECRET,
callbackURL: process.env.CALLBACK_URL,
profileFields: ['id', 'displayName', 'photos', 'email']
},
function (accessToken, refreshToken, profile, done) {
//check user table for anyone with a facebook ID of profile.id
console.log(profile);
User.findOne({
facebookId: profile.id
}, function (err, user) {
if (err) {
return done(err);
}
//No user was found... so create a new user with values from Facebook (all the profile. stuff)
if (!user) {
user = new User({
name: profile.displayName,
email: profile.emails[0].value,
username: profile.username,
provider: 'facebook',
User.findOne({'facebook.id': profile.id } will match because of this next line
facebook: profile._json
});
user.save(function (err) {
if (err) console.log(err);
return done(err, user);
});
} else {
//found user. Return
return done(err, user);
}
});
}
));
This is the code of facebook strategy. I can sign up succesfully using facebook but I am not able to add the user to my database. I have the same code for my google oauth and it seems to work in google.
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.
In passport-facebook Strategy, when I query using Mongoose User.findOne() it doesn't return error or success object. Nodejs when come to this block, escape this block User.findOne(){...} without giving any error and success i.e.silently escape.
passport.use(new FacebookStrategy({
clientID: config.get('facebook.clientID'),
clientSecret: config.get('facebook.clientSecret'),
callbackURL: config.get('facebook.callbackURL'),
passReqToCallback: true,
profileFields: ['id', 'email', 'first_name', 'last_name']
},
function(req, accessToken, refreshToken, profile, done) {
// check if the user is already logged in
let fbInfo = profile._json;
// console.log(fbInfo);
if (!req.user) {
User.findOne({ 'email': fbInfo.email }, function(err, user) {
if (err)
return done(err);
if (user) {
I think you don't make it inside the if statement.
Change if (!req.user) to if (req.user) and it should work.
--
However here is some sample code using Passport Facebook authentication strategy:
passport.use(new FacebookStrategy({
clientID: oauth.facebook.clientID,
clientSecret: oauth.facebook.clientSecret,
callbackURL: oauth.facebook.callbackURL,
// passReqToCallback: true,
profileFields: ['id', 'emails', 'name']
},
function (accessToken, refreshToken, profile, done) {
process.nextTick(function () {
User.findOne({
$or: [
{ 'facebook.id': profile.id },
{ 'email': profile.emails[0].value }
]
}, function (err, user) {
if (err) {
return done(err)
}
if (user) {
if (user.facebook.id == undefined) {
user.facebook.id = profile.id
user.facebook.token = accessToken
user.facebook.email = profile.emails[0].value
user.facebook.name = profile.name.givenName + ' ' + profile.name.familyName
user.save()
}
return done(null, user)
} else {
let newUser = new User()
...
newUser.save(err => {
if (err) {
console.log(err)
throw err
}
return done(null, newUser)
})
}
})
})
}
))
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