I've incorporated the authentication code from this tutorial into my application and everything is working. Now I'm going back to make the database error handling more robust. In the code below (from the tutorial), why do they throw the error if they hit a snag with the save()? Is there a reason not to handle more gracefully? Perhaps something like:
if (err)
return done(err, false, req.flash('signupMessage', 'Encountered database error.'));
From the tutorial:
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function() {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'local.email' : email }, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
if (user) {
return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
} else {
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.local.email = email;
newUser.local.password = newUser.generateHash(password);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));
solution is simple:
newUser.save(function(err) {
if (err) {
return done(err);
}
return done(null, newUser);
});
even in mongoose documentation saving is done without throwing exception.
solution that You're reading is too old: Dec 04, 2013. Why not to read latest documentation from it's pure source?
Read this: http://passportjs.org/docs/configure
BONUS: I would recommend to shorten Your code by using plugin mongoose findOrCreate
Related
I've been searching stackoverflow for reasons why my node.js (express) app hangs on some posts with passport.js.
I've been looking at theese two questions:
More Passport.js woes - hangs on form submission
Passport (local) is hanging on Authentication
My code for creating a user works fine, and looks like this:
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass in the req from our route (lets us check if a user is logged in or not)
},
function(req, email, password, done) {
if (email)
email = email.toLowerCase(); // Use lower-case e-mails to avoid case-sensitive e-mail matching
// asynchronous
process.nextTick(function() {
// if the user is not already logged in:
if (!req.user) {
User.findOne({
where: {
local_email: email
}
}).then(function(user) {
if (user){
return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
}else{
// create the user
User
.build({
local_email: email,
local_password: generateHash(password),
id: 1 //Normal activated user
})
.save()
.then(newUser => {
//Our newly crated user
return done(null, newUser);
})
.catch(error =>{
//Woops something went wrong
return done(error);
})
//var newUser = new User();
//newUser.localemail = email;
//newUser.localpassword = generateHash(password);
//User.create(newUser).then(function(newUser, created) {
// if (!newUser) {
// return done(err);
// }
// if (newUser) {
// return done(null, newUser);
// }
//});
}
});
// if the user is logged in but has no local account...
} else if ( !req.user.local.email ) {
// ...presumably they're trying to connect a local account
// BUT let's check if the email used to connect a local account is being used by another user
User.findOne({
where: {
localemail: email
}
}).then(function(user) {
if (err)
return done(err);
if (user){
return done(null, false, req.flash('loginMessage', 'That email is already taken.'));
// Using 'loginMessage instead of signupMessage because it's used by /connect/local'
} else {
// create the user
var newUser = new User();
newUser.local.email = email;
newUser.local.password = generateHash(password);
User.create(newUser).then(function(newUser, created) {
if (!newUser) {
return done(err);
}
if (newUser) {
return done(null, newUser);
}
});
}
});
} else {
// user is logged in and already has a local account. Ignore signup. (You should log out before trying to create a new account, user!)
return done(null, req.user);
}
});
}));
In my routs file the post to that function looks like this:
// process the signup form
app.post('/signup', passport.authenticate('local-signup', {
successRedirect : '/profile', // redirect to the secure profile section
failureRedirect : '/signup', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
My code for signing in however dosen't work that well. The function in passport.js looks like this:
passport.use('local-login', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass in the req from our route (lets us check if a user is logged in or not)
},
function(req, email, password, done) {
if (email)
email = email.toLowerCase(); // Use lower-case e-mails to avoid case-sensitive e-mail matching
// asynchronous
process.nextTick(function() {
User.findOne({
where: {
local_email: email
}
}).then(function(user){
console.log("TEST: "+user);
// if no user is found, return the message
if (!user){
console.log("no user with the following email: " +email);
return done(null, false, req.flash('loginMessage', 'No user found.'));
}else if(!validPassword(password,user.local_password)){
console.log("wrong password");
return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.'));
// all is well, return user
}else{
return done(null, user);
return;
}
}).catch(function(err){
console.log("woops der skete en fejl: "+err);
console.log("email is: "+email);
console.log("password is: "+password);
return done(err);
});
});
}));
The corresponding route looks like this:
// process the login form
app.post('/login', passport.authenticate('local-login', {
successRedirect : '/', // redirect to the secure profile section
failureRedirect : '/login', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
The above code hangs when the form is being submitted. If I changes the code to:
app.post('/login', function(req, res, next) {
passport.authenticate('local-login', function(err, user, info) {
if (err) { return next(err); }
if (!user) {
return res.render('/login', {
pageTitle: 'Sign in',
form: req.body
});
}
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/');
});
})(req, res, next);
});
I receive an 500.
Any ideas why this isen't working?
Updated with console output:
Executing (default): SELECT `id`, `role`, `local_email`, `local_password`, `facebook_id`, `facebook_token`, `facebook_email`, `facebook_name`, `twitter_id`, `twitter_token`, `twitter_displayname`, `twitter_username`, `google_id`, `google_token`, `google_email`, `google_name`, `createdAt`, `updatedAt` FROM `users` AS `users` WHERE `users`.`local_email` = 'test#local' LIMIT 1;
TEST: [object SequelizeInstance:users]
POST /login 302 87.021 ms - 46
Executing (default): SELECT `id`, `role`, `local_email`, `local_password`, `facebook_id`, `facebook_token`, `facebook_email`, `facebook_name`, `twitter_id`, `twitter_token`, `twitter_displayname`, `twitter_username`, `google_id`, `google_token`, `google_email`, `google_name`, `createdAt`, `updatedAt` FROM `users` AS `users` WHERE `users`.`id` = 1;
Updated with changed passport.deserializeUser
passport.deserializeUser(function(id, done) {
/*User.findById(id, function(err, user) {
done(err, user);
});*/
User.findById(id).then(user => {
if (!user){
done(null);
}else{
done(user);
}
})
});
Your passport.deserializeUser isn't correct. As with all Node.js callbacks, the first argument is reserved for errors, and should be null (or another falsy value) if there aren't any errors. In your case, you're passing user as first argument, which makes Passport think there was an error.
Try this:
passport.deserializeUser(function(id, done) {
User.findById(id)
.then(user => done(null, user))
.catch(done);
});
I am using passport with passport-local-mongoose for register and login users.
This is the code I use to login users:
passport.use(new localStrategy({ usernameField: 'email' }, function(email, password, done) {
User.findOne({ email: email }, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username or password.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect username or password.' });
}
return done(null, user);
});
}));
Everything works fun but user.validPassword.
I define it in user model:
userSchema.methods.validPassword = function(password) {
// What should I write here?
};
Since hash password will be saved into database I do not know how to validate password.
For example this is one user saved into database{
"_id" : ObjectId("5901d55c30967512407cdcd0"),
"salt" : "7e76e1de50856c0a0e219c48609e0c86e8036bd4aa48e5161635f88dd19b695b",
"hash" : "eb78dcb7109454457b0709b6b49f322c69454930e3a6ca90621b1f38d4a068c46a34d0af8ba4f3cb3c3f9c8a30889f54028f182e1a038ab5d642e408117a93b34a519e594f62ea2209ef350f17d388e07476c021fdd66979e5752305b2f41571c830c4e03300cfbddce70443debee06d921900e1f7b9f024ea8d875553f01989e9267b01cc7e9da70f5ee39085527a55c8b6a9f5c7f21e07166797d5446322f52ec79c8e6bfd3e31d5f3953d86a13775da09f7ac3a5a85e5342cd35324bc66055030ca738fa657c50ec4368fe1fd4a26448b460996dc85ddebf90f92796f2b1adf9b905e0590efcadd8326053e354afcc144a155ca7ba1a0f1269cd2c5edec9ef4c643e5ca58310bf3283ed21bb94da6b22c113d19baf091f62bf1776fdcd4bca572cc114ec991d780c18524aad34988d0045f9a1d35e6cda4a6be337d7c8dce8256a240ecac9c7f4ac6c231a3c258f3660b5dd6daf4e67f878fbd9af9e52f9d36266828f564e6ac86f3aed98f7af0bb39017f6080e41cb49237bec6eae77253200750be14e53e79e3fc8d29a3a5cc774905e47bc8df6e5ae9f1b38d9ef738a7cc7890aba4bbea757c694df5faaeed2c692adcc9d8bb0242a5ced98c6a015f5b0b3b475b4a78767a1e9e3c6c9f1bc1be42a835f9e54de3ce223f6190ed457ea972ee4be6f506fd3995411d05247b7102c396c3a16b0d4c26664833d2224191cc",
"username" : "stve45",
"email" : "stebe#companycom",
"name" : "Steve",
"__v" : 0
}
I also use simple passport-local-authenticate.
Any help will be appreciate, thanks a lot.
If you are using passport-local-mongoose, it will create a salt and hash itself by taking your password. So that's why in your user schema you don't have to save the password field.
But the local strategy for passport-local-mongoose is quite different from normal passport local strategy. It is like
passport.use(new LocalStrategy(User.authenticate()));
This will check the entered password and will check it with the salt and hash.
The code you wrote is for normal local strategy. That should not use if you are using passport-local-mongoose.
This is how you should srialize and desirialize passport-local-mongoose:
passport.serializeUser(User.serializeUser());passport.deserializeUser(User.deserializeUser());
I assume you have a method for generating the salt and hash before saving.
In your validPassword method, you call the same method and compare the result from the user's password inout and compare it to what you have in the DB/ If it matches, then you are good.
As an alternative, there are libraries to manage this. I use bcrypt. See a sample here:
adminSchema.pre('save', function(next) {
var user = this;
// only hash the password if it has been modified (or is new)
if (!user.isModified('password')) {
return next();
}
// password changed so we need to hash it (generate a salt)
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if (err) {
return next(err);
}else{
// hash the password using our new salt
bcrypt.hash(user.password, salt, function(err, hash) {
if (err) { return next(err); }
// override the cleartext password with the hashed one
user.password = hash;
next();
});
}
});
});
adminSchema.methods.comparePassword = function(password, cb) {
console.log('comparing password');
bcrypt.compare(password, this.password, function(err, isMatch) {
cb(err, isMatch);
});
};
Here is an example code.Please note that the User.findOne() function is far from being complete.It's there just for you to see where this code stands with respect to yours and to give you an idea of what changes should be made.
passport.use('local.signup', new LocalStrategy({
usernameField:'email', //it can be email or whatever one chooses
passwordField:'password',
confirmField:'password',
passReqToCallback:true//here is the trick.u pass everything you want to do to a callback
},function (req,email, password, done) {
req.checkBody('email','Invalid e-mail address...').notEmpty().isEmail().normalizeEmail();//validate email
req.checkBody('password','Invalid password...').notEmpty().isLength({min:8});//validate pass to be min 8 chars but you can provide it with checking for capital letters and so on and so forth
var errors = req.validationErrors();
if(errors){
var messages = [];
errors.forEach(function (error) {
messages.push(error.msg)
});
return done(null, false, req.flash('error', messages))
}
User.findOne({ email: req.body.email }, function (err, user) {
// Make sure user doesn't already exist
if (user) return done(null, false, {message:'The email address you have
entered is already associated with another account.'
});
I am having the "done is not a function" error. I tried to remove the passReqToCallback parameter but it still doesn't work. I am not sure why please help.
It's on the line: return done(null, false, req.flash('signupMessage', 'That username is already taken.'));
// =========================================================================
// LOCAL SIGNUP ============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
emailField : 'email',
usernameField : 'username',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, username, password, done) {
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function() {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.find({ 'local.email' : email, 'local.username' : username }, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
//if user found.
if (user.length!=0) {
if(user[0].username){
return done(null, false, req.flash('signupMessage', 'That username is already taken.'));
}else{
return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
}
return done(err);
}
else {
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.local.email = email;
newUser.local.username = username;
newUser.local.password = newUser.generateHash(password);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, true);
});
}
});
});
}));
// =========================================================================
// LOCAL LOGIN =============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use('local-login', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'userIdentityField',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) { // callback with email and password from our form
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.isValidUserPassword(email, password, done);
}));
};
I'm new to Nodejs and looking for a user credential management, I see that Passport a good option.
But in Passport's registration strategy, I only see that user information saved is email and password.
I need more user information like full name, job, registration time, last activity time, etc.
So how can I do this in Passport?
Inside of your Passport.js signup/register strategy you should be able to just pass the request object in as the first parameter of that function and Passport will take care of passing your request into your function for you.
So you should be able to use the req.body object, get the data from your form that way and store that to your database.
Below is a more detailed example of how this would work.
passport.use('signup', new LocalStrategy({
passReqToCallback : true
},
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
if (err){
console.log('Error in Signup: ' + err);
return done(err);
}
// already exists
if (user) {
console.log('User already exists');
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();
// set the user's local credentials
newUser.username = username;
newUser.password = createHash(password);
newUser.firstName = req.body.firstName;
newUser.lastName = req.body.lastName;
// 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);
});
);
Here is a tutorial that covers it in a bit more detail. I did change the firstName and lastName parameters from params to variables in the body. But you can either use params or body to get that data into your local strategy.
when making a request for the newUser args other params should be requested from the form body like so
newUser.local.fname = req.body.fname
I found that this worked better. It seemed to be key that the done be after the req, username, password but before the other variables your desired to pass into the function. If done was placed at the end then:
events.js:160 throw er; // Unhandled 'error' event TypeError: done is not a function
would be returned.
// =========================================================================
// LOCAL SIGNUP ============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'username',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, username, password, done, fname, lname, email, security_question, security_answer) {
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function() {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'local.username' : username }, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
if (user) {
return done(null, false, req.flash('signupMessage', 'That username is already taken.'));
} else {
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.local.fname = fname;
newUser.local.lname = lname;
newUser.local.username = username;
newUser.local.email = email;
newUser.local.password = newUser.generateHash(password);
newUser.local.security_question = security_question;
newUser.local.security_answer = newUser.generateHash(security_answer);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));
I'm getting start with Node and Password and I want to modify the default authentication of passport slightly.
I want to add a nickname field to authentication in addition to usual username/email and password fields. This was how I modified:
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'local.email' : email }, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
if (user) {
return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
} else {
/////////////// MODIFIED PART ////////////////
User.findOne({ 'local.nickname' : req.body.nickname }, function(err, user) {
if (user) {
return done(null, false, req.flash('signupMessage', 'That nickname is already taken.'));
/////////////////////////////////////////////
}
});
...
...
But the program exits with :
throw new Error('Can\'t set headers after they are sent.');
^ POST /signup Error: Can't set headers after they are sent. 302 at ServerResponse.OutgoingMessage.setHeader (http.js:691:11)
72ms - 72b at ServerResponse.res.setHeader
(/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/express/node_modules/connect/lib/patch.js:63:22)
at ServerResponse.res.set.res.header (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/express/lib/response.js:527:10)
at ServerResponse.res.location (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/express/lib/response.js:657:8)
at ServerResponse.res.redirect (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/express/lib/response.js:698:8)
at allFailed (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/passport/lib/passport/middleware/authenticate.js:124:20)
at attempt (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/passport/lib/passport/middleware/authenticate.js:231:28)
at Context.delegate.fail (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/passport/lib/passport/middleware/authenticate.js:226:9)
at Context.actions.fail (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/passport/lib/passport/context/http/actions.js:35:22)
at verified (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/passport-local/lib/passport-local/strategy.js:82:30)
Process finished with exit code 8
I have two questions:
How to fix this issue -
Is this the desired way of doing this (authenticating on nickname after username)
Thank you.
The problem is that you are using res.send twice (or anything that send the headers). You could simply debug headers like that in express:
app.use(express.json()); // or anything
...
app.use(function(req, res, next)
app.use(function(req, res, next) {
res.on('header', function() {
console.trace('HEADERS GOING TO BE WRITTEN');
});
next();
});
}
..
app.use(express.router);
see Best way to debug 'Can't set headers after they are sent' error in Express / Node.js?. Like so, you will be able to see what triggers the sending of the headers from the stacks.
That said, I use my, own route for user registration. Here is an adaptation (assuming the file is required and create is added into the router):
exports.create = function (req, res, next) {
User.findOne({
'local.email': req.body.email,
'local.nickname': req.body.nickname
}, function (err, user) {
if (user) {
req.flash('error', 'user already exists');
return res.redirect('/signup'):
}
var user = new User(req.body);
user.provider = 'local';
user.save(function (err) {
if (err) {
console.log(err);
return res.send(err);
}
// manually login the user once successfully signed up
req.logIn(user, function (err) {
if (err) return next(err);
return res.redirect('/');
});
});
});
};
You might also use schema validation . See https://github.com/madhums/node-express-mongoose-demo/blob/master/app/models/user.js
Example :
UserSchema.path('email').validate(function (email, fn) {
var User = mongoose.model('User')
if (this.doesNotRequireValidation()) fn(true)
// Check only when it is a new user or when email field is modified
if (this.isNew || this.isModified('email')) {
User.find({ email: email }).exec(function (err, users) {
fn(!err && users.length === 0)
})
} else fn(true)
}, 'Email already exists')
for the email. do the same for username.