Passport Facebook integration issues - passport.js

So I am having trouble with integrating Facebook into my app.
This is my Facebook auth code :
passport.use('facebook', new FacebookStrategy({
clientID: passportConfig.FACEBOOK_APP_ID,
clientSecret: passportConfig.FACEBOOK_APP_SECRET,
callbackURL: "http://localhost:3000/auth/facebook/callback"
}, function(accessToken, refreshToken, profile, done) {
facebookModel.findOne({'facebook.facebookId' : profile.id}, function(err, user) {
if (err) {
return done(err);
}
if (user) {
done(null, user);
}else {
var facebookUser = new facebookModel();
facebookUser.facebook.facebookId = profile.id;
facebookUser.facebook.token = accessToken;
facebookUser.facebook.name = profile.displayName;
facebookUser.save(function(error){
if (error) {
console.log("Error logging into Facebook.");
}else {
done(null, facebookUser);
}
});
}
});
}));
This is my model :
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var userSchema = new Schema({
local : {
fname : String,
lname : String,
username : String,
email : String,
password : String
},
facebook : {
facebookId : String,
token : String,
name : String
}
});
module.exports = mongoose.model('User', userSchema);
However I keep getting an error.
On the back end
FacebookUser.facebook.facebookId = profile.id;
TypeError: Cannot set property 'facebookId' of undefined
However I do see that my profile object has an id.
On the front-end
GET http://localhost:3000/auth/facebook/callback?code=AQB3NfQB7WP ..... net::ERR_CONNECTION_REFUSED

facebookUser.facebook is undefined
you need declare it, sample :
facebookUser.facebook = {}

Actually I found what was wrong I was using the wrong model.
Changed from facebookModel to userModel.
Fixed the problem.

Related

How do I fix cast error to object Id failed in mongoose?

I'm using passport js for google authentication and when I try going to login page I get this error "CastError: Cast to ObjectId failed for value "xxxxxxxx" (type string) at path "_id" for model "User"". How can I fix this?
Here's my schema code
*
const {Schema,model} = require("mongoose");
const passportLocalMongoose = require("passport-local-mongoose");
const userSchema = new Schema({
username: {
type: String,
unique: true,
},
email: {
type: String,
unique:true,
},
googleid:{
type: String,
}
}, {timestamps:true})
userSchema.plugin(passportLocalMongoose, { usernameField: 'email' });
module.exports = model("User", userSchema);*
here's my google authentication code
const passport = require('passport')
const GoogleStrategy = require('passport-google-oauth2').Strategy
const User = require('../../database/models/user')
require('dotenv').config()
passport.serializeUser((user, done) => {
done(null, user)
console.log(user)
})
passport.deserializeUser((id, done) => {
console.log('chigala')
User.findById(id).then(user => {
done(null, user)
})
})
const params = {
clientID: process.env.GOOGLE_OAUTH_CLIENT_ID,
clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET,
callbackURL: 'http://localhost:5000/api/google/auth',
passcallbackURL: true
}
const Strategy = new GoogleStrategy(
params,
async (req, accessToken, refreshToken, profile, done) => {
console.log(profile)
try {
const currentUser = await User.findOne({ email: profile.emails[0].value })
if (currentUser) {
// console.log(`this is the current user:${currentUser}`)
if (currentUser.googleId) {
done(null, currentUser)
return
}
currentUser.googleId = profile.id
currentUser.save()
done(null, currentUser)
} else {
const user = await User.create({
googleId: profile.id,
email: profile.emails[0].value
})
done(null, user)
}
} catch (err) {
console.log(err)
}
}
)
passport.use(Strategy)
The default findById method tries to cast id to the MongoDB _id format so this throws an error.
In the deserialize function you can use new objectId(id) to cast id to the MongoDB _id format.
const objectId= require('mongodb').ObjectId; //here
const passport = require('passport')
const GoogleStrategy = require('passport-google-oauth2').Strategy
const User = require('../../database/models/user')
require('dotenv').config()
passport.deserializeUser((id, done) => {
console.log('chigala')
User.findById(new objectId(id)).then(user => { //here
done(null, user)
})
})

Node.js Passport: Twitter not returning user's email during authentication.

I've requested permission from apps.twitter.com and the authentication process is working successfully but twitter is not returning the user's email.
Every other detail were returned (id, token, username, displayName) but Email remains null.
I will post some of my code below for context:
This is my mongoose database schema:
var mongoose = require('mongoose');
//Defining the database scheme.
var userSchema = mongoose.Schema ({
twitter: {
id: Number,
token: String,
email: {type:String, unique:true},
username: String,
displayName: String,
signupDate: { type: Date, default: Date.now }
}
});
//make the model available public
module.exports = mongoose.model('User', userSchema);
My passport.js:
//passport.js
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var TwitterStrategy = require('passport-twitter').Strategy;
// load up the user model
var User = require('../app/models/user');
// load the auth variables
var configAuth = require('./auth');
module.exports = function(passport) {
// used to serialize the user for the session
passport.serializeUser(function(user, done) {
done(null, user.id);
});
// used to deserialize the user
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
// code for login (use('local-login', new LocalStategy))
// code for signup (use('local-signup', new LocalStategy))
// code for facebook (use('facebook', new FacebookStrategy))
// =========================================================================
// TWITTER =================================================================
// =========================================================================
passport.use(new TwitterStrategy({
consumerKey : configAuth.twitterAuth.consumerKey,
consumerSecret : configAuth.twitterAuth.consumerSecret,
callbackURL : configAuth.twitterAuth.callbackURL,
userProfileURL: "https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true",
passReqToCallback : true,
},
function(token, tokenSecret, profile, done) {
// make the code asynchronous
// User.findOne won't fire until we have all our data back from Twitter
process.nextTick(function() {
User.findOne({ 'twitter.id' : profile.id }, function(err, user) {
// if there is an error, stop everything and return that
// ie an error connecting to the database
if (err)
return done(err);
// if the user is found then log them in
if (user) {
return done(null, user); // user found, return that user
} else {
// if there is no user, create them
var newUser = new User();
// set all of the user data that we need
newUser.twitter.id = profile.id;
newUser.twitter.token = token;
newUser.twitter.username = profile.username;
newUser.twitter.displayName = profile.displayName;
newUser.twitter.email = profile.email;
// save our user into the database
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));
};
I'd love to know what I'm getting wrong so I can return user's email successfully. Thanks
Please check link
comment by #rvetere.
You get an email from the profile :
profile.emails[0].value
Also, check Your userProfileURL
export default new TwitterStrategy({
consumerKey : process.env.AUTH_TWITTER_CONSUMER_KEY,
consumerSecret : process.env.AUTH_TWITTER_CONSUMER_SECRET,
callbackURL : process.env.AUTH_TWITTER_CALLBACK_URL,
userProfileURL : 'https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true',
passReqToCallback : true,
},
in passport-twitter you can implement this code
passport.use(
new TwitterStrategy (
...keys,
includeEmail: true,
)
)
you should config the app settings in your twitter developer to check the permission for account email.

Facebook Passport Strategy returning 500 error

I am trying to make a Logged in or Signed In user to connect their account with facebook using passport.js facebook strategy, and save their profile photo, id, gender, timeline cover and token according the userSchema (as made in user.jsmodel shown below.
I tried many combinations but still either getting 500error from facebook, or if showing facebook auth, facebook can't return (the code combination, I tried) and save the object.
PS : I had entered correct callback URL in facebook
PPS: Please refer my UPDATED routes.js and updated passport.js below.
This is my routes.js file:
app.get('/auth/connect/facebook', passport.authenticate('facebook-connect', { authType: 'rerequest', scope: ['id', 'cover', 'gender', 'photos'] }));
app.get('/auth/connect/facebook/callback',
passport.authenticate('facebook-connect', {
successRedirect: '/profile/configure',
failureRedirect: '/profile/congigure'
// failureFlash: true
}));
My passport.js file of facebook-connect:
passport.use('facebook-connect', new FacebookStrategy({
clientID: configAuth.facebookAuth.clientID,
clientSecret: configAuth.facebookAuth.clientSecret,
callbackURL: configAuth.facebookAuth.callbackURL,
profileFields: ['id', 'cover', 'gender', 'photos'],
enableProof: true
},
function(token, refreshToken, profile, cb) {
process.nextTick(function() {
User.findOne({ 'local.facebook.id': profile.id }, function(err, user) {
if (err)
return cb(err);
if (user) {
return cb(null, false, req.flash('fbflash', 'This facebook user is already connected with an account at eBird.'));
} else {
user.local.facebook.id = profile.id;
user.local.facebook.token = token;
user.local.profile.gender = profile.gender;
user.local.profile.herobg = profile.cover;
user.local.profile.dp = user.local.profile.dp ? user.local.profile.dp : profile.photos[0].value;
if (user.local.profile.dp == '') {
if (user.local.profile.gender == 'male') {
user.local.profile.dp = 'http://res.cloudinary.com/pinterested222/image/upload/v1487659283/an-av-3_jxrhwc.png';
}
if (user.local.profile.gender == 'female') {
user.local.profile.dp = 'http://res.cloudinary.com/pinterested222/image/upload/v1487770814/female-avatar_vvyvtj.png';
}
}
user.save(function(err) {
if (err)
throw err;
return cb(null, user);
});
}
});
});
}));
My user.js model:
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
var DateOnly = require('mongoose-dateonly')(mongoose);
var shortid = require('shortid');
var uniqueValidator = require('mongoose-unique-validator');
var userSchema = mongoose.Schema({
_id: {
type: String,
default: shortid.generate
},
local: {
email: String,
username: { type: String, unique: true },
firstname: String,
surname: String,
name: String,
role: { type: String, default: 'user' },
department: String,
pno: Number,
password: String,
verified: { type: Boolean, default: false },
profile: {
dp: String,
createdAt: { type: Date, default: Date.now },
herobg: String,
location: String,
website: String,
gender: String,
birthday: DateOnly,
lastlogin: { type: Date },
notifications: {
name: String,
namedp: String,
type: { type: String },
date: { type: Date, default: Date.now },
read: { type: Boolean, default: false }
}
},
facebook: {
id: String,
token: String
}
}
});
userSchema.plugin(uniqueValidator, { message: '{Path}:{VALUE} is already taken.' });
userSchema.methods.generateHash = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
userSchema.methods.validPassword = function(password) {
return bcrypt.compareSync(password, this.local.password);
};
// userSchema.methods.bellTimesAgo = function(date);
module.exports = mongoose.model('User', userSchema);
The error, it's throwing me:
The www.facebook.com page isn’t working
www.facebook.com is currently unable to handle this request.
HTTP ERROR 500
Any help would be appreciated,
Thanks.
UPDATE - 1
I read (& from passportjs docs) about passport.authorize() and updated my passport.js file accordig to passport.authorize() and also updated my routes, but still the same problem.
Here is my updated passport.js:
// Facebook Strategy Updated using authorize
passport.use(new FacebookStrategy({
clientID: configAuth.facebookAuth.clientID,
clientSecret: configAuth.facebookAuth.clientSecret,
callbackURL: configAuth.facebookAuth.callbackURL,
// profileFields: ['id', 'cover', 'gender', 'photos'],
// enableProof: true,
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
process.nextTick(function() {
if (!req.user) {
User.findOne({ 'local.facebook.id': profile.id }, function(err, user) {
if (err)
return done(err);
if (user) {
return done(null, false, req.flash('fbflash', 'This facebook user is already connected with an account at eBird.'));
} else {
user.local.facebook.id = profile.id;
user.local.facebook.token = accessToken;
user.local.profile.gender = profile.gender;
user.local.profile.herobg = profile.cover;
user.local.profile.dp = user.local.profile.dp ? user.local.profile.dp : profile.photos[0].value;
if (user.local.profile.dp == '') {
if (user.local.profile.gender == 'male') {
user.local.profile.dp = 'http://res.cloudinary.com/pinterested222/image/upload/v1487659283/an-av-3_jxrhwc.png';
}
if (user.local.profile.gender == 'female') {
user.local.profile.dp = 'http://res.cloudinary.com/pinterested222/image/upload/v1487770814/female-avatar_vvyvtj.png';
}
}
user.save(function(err) {
if (err)
throw err;
return done(null, user);
});
}
});
} else {
var user = req.user;
user.local.facebook.id = profile.id;
user.local.facebook.token = accessToken;
user.local.profile.gender = profile.gender;
user.local.profile.herobg = profile.cover;
user.local.profile.dp = user.local.profile.dp ? user.local.profile.dp : profile.photos[0].value;
if (user.local.profile.dp == '') {
if (user.local.profile.gender == 'male') {
user.local.profile.dp = 'http://res.cloudinary.com/pinterested222/image/upload/v1487659283/an-av-3_jxrhwc.png';
}
if (user.local.profile.gender == 'female') {
user.local.profile.dp = 'http://res.cloudinary.com/pinterested222/image/upload/v1487770814/female-avatar_vvyvtj.png';
}
}
user.save(function(err) {
if (err)
throw err;
return done(null, user);
});
}
});
}));
Here is my updated routes.js:
app.get('/auth/connect/facebook', passport.authorize('facebook', { authType: 'rerequest', scope: ['id', 'cover', 'gender', 'photos'] }));
app.get('/auth/connect/facebook/callback',
passport.authorize('facebook', {
successRedirect: '/profile/configure',
failureRedirect: '/profile/configure'
// failureFlash: true
})
);
Here is the snapshot of my app callback settings from Facebook:
Snapshot of the error, facebook keeps throwing in:
Passport.js documentation said:
Values for the scope option are provider-specific. Consult the provider's documentation for details regarding supported scopes.
If you check allowed permissions in Facebook documentation, you will not find such permissions as 'id', 'cover', 'gender', 'photos'. These items are part of a person's public profile.
So, you should change scope in routes.js from:
scope: ['id', 'cover', 'gender', 'photos']
to:
scope: ['public_profile']
or don't specify scope, because public_profile is default facebook permission.
P.S. I told about your "update 1" code version.
Taking inspiration from #anton-novik, I fixed the bug.
The problem was in my routes.js file. First have a look at my routes.js file above, and then follow the code below:
app.get('/auth/connect/facebook', ensureLoggedIn('/login'), passport.authorize('facebook', { authType: 'rerequest' }));
app.get('/auth/connect/facebook/callback',
passport.authenticate('facebook', {
successRedirect: '/profile',
failureRedirect: '/profile/settings',
failureFlash: true
})
);
There was no need of scope for the request I was making was already approved by Facebook for every app.
And then updated my passport.js file to look like this:
// // Facebook Strategy
passport.use(new FacebookStrategy({
clientID: configAuth.facebookAuth.clientID,
clientSecret: configAuth.facebookAuth.clientSecret,
profileFields: ['id', 'picture.type(large)', 'gender', 'cover'],
callbackURL: configAuth.facebookAuth.callbackURL,
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
process.nextTick(function() {
// User is not logged in yet
if (!req.user) {
User.findOne({ 'local.facebook.id': profile.id }, function(err, user) {
if (err)
return done(err);
if (user) {
if (!user.facebook.token) {
user.facebook.token = accessToken;
user.facebook.name = profile.displayName;
user.facebook.email = profile.emails[0].value;
user.save(function(err) {
if (err) throw err;
return done(null, user);
});
}
return done(null, user);
} else {
// User should be created here
// and saved to mongoose
}
});
}
//else user is logged in and needs to be merged
else {
console.log(profile); //display the returned json from fb
// Connect the user and save the details, since the user already exsists
var user = req.user;
user.local.facebook.id = profile.id;
user.local.facebook.token = accessToken;
user.local.profile.gender = profile.gender;
user.local.profile.dp = profile.photos[0].value;
user.local.profile.herobg = profile._json.cover.source;
user.save(function(err) {
if (err)
throw err;
return done(null, user);
});
}
});
}));
Hope, it may help someone. :)

Inserting an array in mongodb using mongoose in a schema where other fields are already added from user registration

I am creating an application for online course.
I have created a schema for user registration. In the schema, I also want to add the name of courses a user in enrolled. Course Name being an array.
User registration is successful. after that I have created a route for /dashboard, where the user sends the POST request to add the course name. That course should be added in the same registration schema field for course Enrolled. However When I save a new object of registration schema, it creates a new document field courseEnrolled field. I want this POST request value to be added in the user's document field of courseEnrolled as an array.
Here is how I have defined my registration schema. Its name in account.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var passportLocalMongoose = require('passport-local-mongoose');
var courseSchema = new Schema({ courseName : String });
var Account = new Schema({
username: {
type: String,
unique: true
},
password: String,
email: String,
firstName: String,
lastName: String,
courseEnrolled: [{courseName : String}]
});
Account.plugin(passportLocalMongoose);
module.exports = mongoose.model('Account', Account);
Here is my passport registration . register.js
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var User = require('../models/account');
var bCrypt = require('bcrypt-nodejs');
var course = require('../models/courseEnrollment');
module.exports = function(passport){
passport.use('register', new LocalStrategy({
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, username, password, done) {
findOrCreateUser = function(){
// find a user in Mongo with provided username
User.findOne({ 'username' : username }, function(err, user) {
// In case of any error, return using the done method
if (err){
console.log('Error in SignUp: '+err);
return done(err);
}
// already exists
if (user) {
console.log('User already exists with username: '+username);
return done(null, false, req.flash('message','User Already Exists'));
} else {
// if there is no user with that email
// create the user
var newUser = new User();
var newCourse = new course();
// set the user's local credentials
newUser.username = username;
newUser.password = createHash(password);
newUser.email = req.body.email;
newUser.firstName = req.body.firstName;
newUser.lastName = req.body.lastName;
newUser.courseEnrolled = req.body.courseEnrolled;
// save the user
newUser.save(function(err) {
if (err){
console.log('Error in Saving user: '+err);
throw err;
}
console.log('User Registration succesful');
return done(null, newUser);
});
}
});
};
// Delay the execution of findOrCreateUser and execute the method
// in the next tick of the event loop
process.nextTick(findOrCreateUser);
})
);
// Generates hash using bCrypt
var createHash = function(password){
return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null);
}
}
I can register a user successfully. After that I have a route for /dashboard, where I handle the POST request to add a course.
Here is the snippet of my /dashboard handling POST request.
var User = require('../models/account');
/* POST dashboard Page */
router.post('/dashboard', isAuthenticated, function (req, res) {
sess = req.session.passport.user;
console.log('session value is: ' + sess);
var newUser = new User();
console.log('newUser id is: ' + newUser._id);
var currentUser = req.user._id;
console.log('current User id is: ' + currentUser);
var myUser = req.user;
console.log('myUsers value is: ' + myUser);
var myUserCourse = req.user.courseEnrolled;
if (sess == currentUser) {
//var newCourse = new course();
console.log('request received: ' + req.body.courseEnrolled);
req.user.courseEnrolled = req.body.courseEnrolled;
newUser.save(function (err, data) {
if(error)
throw error;
else {
console.log('course Updated');
}
});
res.render('home', {user: req.user});
}
});
This newUser.save() function creates a new document in the mongodb and store the courseEnrolled. I want to store the value of req.body.courseEnrolled in the same document field where other user value is defined.
This is getting stored in collection:- 'accounts' for the user
{
"_id" : ObjectId("57f95afd9c78b91c69334f0d"),
"lastName" : "Nehra",
"firstName" : "Ashish",
"email" : "ashish.nehra#stanford.edu",
"password" : "$2a$10$YzLvbQTHFtq5l0ooP0njOux94Rp.pm.Pkb/TugBnCSTUJNhBBonLG",
"username" : "ashish",
"courseEnrolled" : [
"about to change something now"
],
"__v" : 1
}
And there is a new document being created like this in the same collection.
{
"_id" : ObjectId("5803fc4342ca1d3167102300"),
"courseEnrolled" : [ ],
"__v" : 0
}
This is logical because first you do a it on various user objects (req.user / new user):
**var newUser = new User();
This will create a new User object, and then:
newUser.save(function (err, data) {
This will save the newly created user into a new document. If you want to use the .save, rewrite it to (reused your own code):
User.findOne({ 'username' : username }, function(err, user) {
// In case of any error, return using the done method
if (err){
console.log('Error in SignUp: '+err);
return done(err);
}
user.courseEnrolled = req.body.courseEnrolled;
user.save(function (err, data) {
if(err)
throw err;
else {
console.log('course Updated');
}
});

MongooseJS cannot retrieve data after saving it

I have a simple ExpressJS app and I'm trying to add user authentication to it using passport, passport-local and passport-local-mongoose, but after I save a user's data on db, mongoose fails to retrieve it, although I can see the data through mongo cli.
At the main express module I have:
// Passport session setup.
var User = require("./models/user");
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
My User model module have:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var passportLocalMongoose = require('passport-local-mongoose');
// User Schema
var User = new Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true}
});
User.plugin(passportLocalMongoose);
module.exports = mongoose.model('User', User);
At my routes module, I create a user with the following code:
router.post('/register', function(req, res) {
var u = req.body.username;
var p = req.body.password;
console.log('Creating account for ' + u + ':' + p);
// Store client on DB
var usr = new User({ username: u, password: p });
usr.save(function(err) {
if(err) {
console.log(err);
return res.render('register', {title: 'Register', error: err.userMessage});
} else {
console.log('user: ' + usr.username + " saved.");
passport.authenticate('local')(req, res, function () {
return res.redirect('/dashboard');
});
}
});
});
But when I try to authenticate a user with the passport.authenticate method, mongoose cannot find the user at the db, as I can see on the mongoose logs:
Login request # 1422120637815
Mongoose: users.findOne({ username: 'e#e.com' }) { fields: undefined }
But at mongo cli I can see the data:
> db.User.find()
{ "username" : "e#e.com", "password" : "123", "_id" : ObjectId("54c3d688fc71a4001db30612"), "__v" : 0 }
Mongoose is looking for a collection named 'users', but it looks like your data is stored in 'User'. The API for model() is mongoose#model(name, [schema], [collection], [skipInit]). so you can force the collection name by adding a third parameter e.g.
module.exports = mongoose.model('User', User, 'User');
EDIT1:
Try using the built-in register() method in mongoose-local
router.post('/register', function(req, res) {
var u = req.body.username;
var p = req.body.password;
console.log('Creating account for ' + u + ':' + p);
// Store client on DB
Account.register(new Account({ username: req.body.username }), req.body.password, function(err) {
if(err) {
console.log(err);
return res.render('register', {title: 'Register', error: err.userMessage});
} else {
passport.authenticate('local')(req, res, function () {
return res.redirect('/dashboard');
});
}
});
});

Resources