How to authenticate a user server-side with Passport JS? - node.js

I want to automatically generate user accounts by generating a random username and password, and then the user is logged in automatically (the user doesn't know his username/password, his browser just stores the session cookie).
Passport functions as middleware, so how can I authenticate the user I just generated? Or, would it be better to somehow redirect to my app.post('/login') route and send those variables? (But somehow sending those to the browser, just to be sent back to the server doesn't seem very secure or efficient).
app.get('/signup', function(req, res) {
if(req.isAuthenticated()) { res.redirect('/'); }
else {
var today = new Date();
var weekDate = new Date();
weekDate.setDate(today.getDate() + 7);
var key1 = Math.random().toString();
var key2 = Math.random().toString();
var hash1 = crypto.createHmac('sha1', key1).update(today.valueOf().toString()).digest('hex');
var hash2 = crypto.createHmac('sha1', key2).update(weekDate.valueOf().toString()).digest('hex');
var newUser = new models.User({
username: hash1,
password: hash2,
signupDate: today,
accountStatus: 0,
expirationDate: weekDate,
});
newUser.save(function(err) {
if(err) {}
console.log("New user created.");
//HOW CAN I PASS USERNAME AND PASSWORD ARGUMENTS???
passport.authenticate('local')();
res.redirect('/login');
})
}
});

Replace your call to passport.authenticate('local')(); with
req.logIn(user, function(err) {
if (err) { return next(err); }
//copied from the docs, you might want to send the user somewhere else ;)
return res.redirect('/users/' + user.username);
});
and let me know how that goes.

the answer by rdrey was very helpful. One detail that might be obvious to most but was not to me is that model .save () gets err and the record in the callback. So the pattern in its entirety is
newuser.save(function(err,user) {
req.logIn(user, function(err) {
if (err) { return next(err); }
//copied from the docs, you might want to send the user somewhere else ;)
return res.redirect('/users/' + user.username);
});

Related

Can't set headers already sent node.js/express

Whenever I go this URL for the second time, I always get
can't set headers already sent
app.post('/auth/facebook', function(req, res, next) {
// What should I do with the access token?
User.findOne({ facebook: req.body.facebookId}, function(err, user) {
// if user does exist then simply send the token back to the client
if (user) {
return res.send({ token: createJWT(user) })
}
// if user doesnt exist
var user = new User();
user.email = req.body.email;
user.facebook = req.body.facebookId;
user.first_name = req.body.first_name;
user.last_name = req.body.last_name;
user.save(function(err) {
return res.send({ token: createJWT(user)})
});
});
});
Is it because I send the token twice?
Yes, this error occurs because you are using the res.send to set response headers twice. The first time you set them, even if it is inside the if bracket, it is the normal use scenario (most times you will have a user returned) so, the second time you call it (this is always executed), you have already set them(when user exists - that is most of the times).
I think that happens with any response headers, such as res.json that KibGzr suggested. That as general advice. Concerning your problem, I would bracket the scenario that there is no user, and inside that I would execute the logic of creating the new user. Then outside the bracket, I would set the headers, as I would always want to sent the token. Like this :
app.post('/auth/facebook', function(req, res, next) {
// What should I do with the access token?
User.findOne({ facebook: req.body.facebookId}, function(err, user) {
// if user doesnt exist create one
if (!user) {
var user = new User();
user.email = req.body.email;
user.facebook = req.body.facebookId;
user.first_name = req.body.first_name;
user.last_name = req.body.last_name;
user.save(function(err) {
if(err){
console.log(err);
}
});
//Then send the token as a user exists anyway
return res.send({ token: createJWT(user) });
}
});
So the general idea is, set the headers once per scenario - make sure no response is set twice per scenario. Hope I helped.
The user object can be either null or a user.Put the logic inside a if else block makes it easier to read the code.Always console log the user object and req.body while dealing with authentication and signing up logic.Makes your life easier.
app.post('/auth/facebook', function (req, res, next) {
// What should I do with the access token?
User.findOne({facebook: req.body.facebookId}, function (err, user) {
// if user does exist then simply send the token back to the client
console.log(user);
if (user != null) {
return res.send({token: createJWT(user)})
} else {
// if user doesnt exist
var user = new User();
user.email = req.body.email;
user.facebook = req.body.facebookId;
user.first_name = req.body.first_name;
user.last_name = req.body.last_name;
user.save(function (err) {
return res.send({token: createJWT(user)})
});
}
});

mongo not showing users even though I "know" they exist

I am trying to implement a authentication system for my website using MEAN however I have run into a relatively strange problem. I am able to register users and duplicate usernames can be identified. However, I cannot get logging into the website working. When I search the mongo database using the command line, I do not get anything. This is what my mongo output looks like.
>> show users
>>
The database has the username somewhere... so how do I get the users to be properly displayed? Why is that user is undefined when I try to log in even though I know the username is in the database?
var crypto = require('crypto');
var mongoose = require('mongoose');
var User = mongoose.model('User');
function hashPW(pwd) {
return crypto.createHash('sha256').update(pwd).digest('base64').toString();
};
module.exports.signup = function (req,res) {
var user = new User({username:req.body.usernmae});
console.log('made it here');
user.set('hashed_password', hashPW(req.body.password));
user.set('email', req.body.email);
user.save(function (err) {
if (err) {
try {
if (err.code==11000) res.render('signup', {message: 'Sorry, someone has that username already.'})
} catch(e) {
}
console.log(err);
//res.redirect('/signup');
} else {
req.session.user = user.id;
req.session.username = user.username;
req.session.msg = 'Authenticated as ' + user.username;
res.redirect('/');
}
});
};
module.exports.login = function (req,res) {
User.findOne({ username: req.body.username })
.exec(function(err,user) {
console.log(user);
console.log(err);
console.log(hashPW(req.body.password.toString()));
if (!user) {
err = 'User Not Found.';
} else if ( user.password === hashPW( req.body.password.toString() ) ) {
req.session.regenerate(function() {
req.session.user = user.id;
req.session.username = user.username;
req.session.msg = 'Authenticated as ' + user.username;
res.redirect('/');
});
} else {
err = 'Authentication failed.';
}
if (err) {
console.log(err);
req.session.regenerate(function() {
req.session.msg = err;
res.redirect('/login');
});
}
});
};
I notice that there's a typo in the provided code.
var user = new User({username:req.body.usernmae});
Should likely read
var user = new User({username:req.body.username});
This probably meant the name failed to set thus putting a junk user into your DB.
Also, regarding your command in the Mongo Shell, Neil's answer covered that the show command is not actually useful here. The reference for db.collection.find() is here.
silly mistake. the field is not password but hashed_password.
{ email: 'somerandomemail#gmail.com',
hashed_password: 'A8ctR3JAA84DWTmYXEAhxEEP1bTtAidaoyWArKHtk2g=',
username: 'Szpok',
_id: 54c09c458c4eccc90b9c4bb5,
__v: 0 }

Passport.Js doesn't let me create two different authentication routes

I am trying to use two different basic authentication conditions for two different apis. But the moment I add the second authentication condition in my project, Passport.Js doesn't let me authenticate at all. It keeps saying wrong password. Below is the code: Can anybody suggest what's wrong with it? I am sorry if this is a noob question. I have just begun working with node so the entire framework is new to me.
// Load required packages
var passport = require('passport');
var passport2= require('passport');
var BasicStrategy = require('passport-http').BasicStrategy;
var BasicStrategy2 = require('passport-http').BasicStrategy;
var User = require('../models/useridentity');
var User2 = require('../models/useridentity');
passport.use(new BasicStrategy(
function(username, password, callback) {
User.findOne({ username: username }, function (err, user) {
if (err) { return callback(err); }
// No user found with that username
if (!user) { return callback(null, false); }
// Make sure the password is correct
user.verifyPassword(password, function(err, isMatch) {
if (err) { return callback(err); }
// Password did not match
if (!isMatch) { return callback(null, false); }
// Success
return callback(null, user);
});
});
}
));
passport2.use(new BasicStrategy2(
function(user, pass, callback) {
User2.findOne({ useremail: user }, function (err, user) {
if (err) { return callback(err); }
// No user found with that username
if (!user) { return callback(null, false); }
// Make sure the password is correct
user.verifyPassword(pass, function(err, isMatch) {
if (err) { return callback(err); }
// Password did not match
if (!isMatch) { return callback(null, false); }
// Success
return callback(null, user);
});
});
}
));
exports.isAuthenticated = passport.authenticate('basic', { session : false });
exports.isAuthenticated2 = passport2.authenticate('basic',{session:false});
Requiring same modules multiple times makes no sense - due to module caching you are likely receiving the same object. You can name the strategies in use which enables using same strategy with different configuration:
var passport = require('passport'),
BasicStrategy = require('passport-http').BasicStrategy;
passport.use('auth1', new BasicStrategy(...));
passport.use('auth2', new BasicStrategy(...));
app.post('/api1', passport.authenticate('auth1'), function(req, res) { ... });
app.post('/api2', passport.authenticate('auth2'), function(req, res) { ... });

How to reset / change password in Node.js with Passport.js?

I use Passport.js in Node.js to create a login system. Everything is ok, but I do not know how to reset user password when they forget their password or they want to change it.
User model in MongoDB
var UserSchema = new Schema({
email: String,
username: String,
provider: String,
hashed_password: String,
salt: String,
});
Didn't really like the idea of hitting my database to store tokens, especially when you want to be creating and verifying tokens for many actions.
Instead I decided to copy how Django does it:
convert timestamp_today to base36 as today
convert user.id to base36 as ident
create hash containing:
timestamp_today
user.id
user.last_login
user.password
user.email
salt the hash with a hidden secret
create a route like : /change-password/:ident/:today-:hash
We test the req.params.timestamp in order to simply test if it's valid for today, cheapest test first. fail first.
Then we find the user, fail if it doesn't exist.
Then we generate the hash again from above, but with the timestamp from req.params
The reset link becomes invalid if :
they remember their password and login (last_login changes)
they're actually still logged in and:
just change their password (password changes)
just change their email (email changes)
tomorrow arrives (timestamp changes too much)
This way:
you're not storing these ephemeral things in your database
when the purpose of the token is to change the state of a thing, and that things state changed, then the purpose of the token is no longer securely relevant.
I tried to use node-password-reset as Matt617 suggested but didn't really care for it. It's about the only thing that's coming up in searches currently.
So some hours digging around, I found it easier to implement this on my own. In the end it took me about a day to get all the routes, UI, emails and everything working. I still need to enhance security a bit (reset counters to prevent abuse, etc.) but got the basics working:
Created two new routes, /forgot and /reset, which don't require the user to be logged in to access.
A GET on /forgot displays a UI with one input for email.
A POST on /forgot checks that there is a user with that address and generates a random token.
Update the user's record with the token and expiry date
Send an email with a link to /reset/{token}
A GET on /reset/{token} checks that there is a user with that token which hasn't expired then shows the UI with new password entry.
A POST on /reset (sends new pwd and token) checks that there is a user with that token which hasn't expired.
Update the user's password.
Set the user's token and expiry date to null
Here's my code for generating a token (taken from node-password-reset):
function generateToken() {
var buf = new Buffer(16);
for (var i = 0; i < buf.length; i++) {
buf[i] = Math.floor(Math.random() * 256);
}
var id = buf.toString('base64');
return id;
}
Hope this helps.
EDIT:
Here's the app.js. Note I'm keeping the entire user object in the session. I plan on moving to couchbase or similar in the future.
var express = require('express');
var path = require('path');
var favicon = require('static-favicon');
var flash = require('connect-flash');
var morgan = require('morgan');
var cookieParser = require('cookie-parser');
var cookieSession = require('cookie-session');
var bodyParser = require('body-parser');
var http = require('http');
var https = require('https');
var fs = require('fs');
var path = require('path');
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var app = express();
app.set('port', 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
var cookies = cookieSession({
name: 'abc123',
secret: 'mysecret',
maxage: 10 * 60 * 1000
});
app.use(cookies);
app.use(favicon());
app.use(flash());
app.use(morgan());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(cookieParser());
app.use(passport.initialize());
app.use(passport.session());
app.use(express.static(path.join(__dirname, 'public')));
module.exports = app;
passport.use(new LocalStrategy(function (username, password, done) {
return users.validateUser(username, password, done);
}));
//KEEP ENTIRE USER OBJECT IN THE SESSION
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
//Error handling after everything else
app.use(logErrors); //log all errors
app.use(clientErrorHandler); //special handler for xhr
app.use(errorHandler); //basic handler
http.createServer(app).listen(app.get('port'), function () {
console.log('Express server listening on HTTP port ' + app.get('port'));
});
EDIT:
Here are the routes.
app.get('/forgot', function (req, res) {
if (req.isAuthenticated()) {
//user is alreay logged in
return res.redirect('/');
}
//UI with one input for email
res.render('forgot');
});
app.post('/forgot', function (req, res) {
if (req.isAuthenticated()) {
//user is alreay logged in
return res.redirect('/');
}
users.forgot(req, res, function (err) {
if (err) {
req.flash('error', err);
}
else {
req.flash('success', 'Please check your email for further instructions.');
}
res.redirect('/');
});
});
app.get('/reset/:token', function (req, res) {
if (req.isAuthenticated()) {
//user is alreay logged in
return res.redirect('/');
}
var token = req.params.token;
users.checkReset(token, req, res, function (err, data) {
if (err)
req.flash('error', err);
//show the UI with new password entry
res.render('reset');
});
});
app.post('/reset', function (req, res) {
if (req.isAuthenticated()) {
//user is alreay logged in
return res.redirect('/');
}
users.reset(req, res, function (err) {
if (err) {
req.flash('error', err);
return res.redirect('/reset');
}
else {
req.flash('success', 'Password successfully reset. Please login using new password.');
return res.redirect('/login');
}
});
});
Here the implementation of airtonix
const base64Encode = (data) => {
let buff = new Buffer.from(data);
return buff.toString('base64');
}
const base64Decode = (data) => {
let buff = new Buffer.from(data, 'base64');
return buff.toString('ascii');
}
const sha256 = (salt, password) => {
var hash = crypto.createHash('sha512', password);
hash.update(salt);
var value = hash.digest('hex');
return value;
}
api.post('/password-reset', (req, res) => {
try {
const email = req.body.email;
// Getting the user, only if active
let query = AccountModel.where( {username: email, active: true} );
query.select("_id salt username lastLoginDate");
query.findOne((err, account) => {
if(err) {
writeLog("ERROR", req.url + " - Error: -1 " + err.message);
res.status(500).send( { error: err.message, errnum: -1 } );
return;
}
if(!account){
writeLog("TRACE",req.url + " - Account not found!");
res.status(404).send( { error: "Account not found!", errnum: -2 } );
return;
}
// Generate the necessary data for the link
const today = base64Encode(new Date().toISOString());
const ident = base64Encode(account._id.toString());
const data = {
today: today,
userId: account._id,
lastLogin: account.lastLoginDate.toISOString(),
password: account.salt,
email: account.username
};
const hash = sha256(JSON.stringify(data), process.env.TOKENSECRET);
//HERE SEND AN EMAIL TO THE ACCOUNT
return;
});
} catch (err) {
writeLog("ERROR",req.url + " - Unexpected error during the password reset process. " + err.message);
res.status(500).send( { error: "Unexpected error during the password reset process :| " + err.message, errnum: -99 } );
return;
}
});
api.get('/password-change/:ident/:today-:hash', (req, res) => {
try {
// Check if the link in not out of date
const today = base64Decode(req.params.today);
const then = moment(today);
const now = moment().utc();
const timeSince = now.diff(then, 'hours');
if(timeSince > 2) {
writeLog("ERROR", req.url + " - The link is invalid. Err -1");
res.status(500).send( { error: "The link is invalid.", errnum: -1 } );
return;
}
const userId = base64Decode(req.params.ident);
// Getting the user, only if active
let query = AccountModel.where( {_id: userId, active: true} );
query.select("_id salt username lastLoginDate");
query.findOne((err, account) => {
if(err) {
writeLog("ERROR", req.url + " - Error: -2 " + err.message);
res.status(500).send( { error: err.message, errnum: -2 } );
return;
}
if(!account){
writeLog("TRACE", req.url + " - Account not found! Err -3");
res.status(404).send( { error: "Account not found!", errnum: -3 } );
return;
}
// Hash again all the data to compare it with the link
// THe link in invalid when:
// 1. If the lastLoginDate is changed, user has already do a login
// 2. If the salt is changed, the user has already changed the password
const data = {
today: req.params.today,
userId: account._id,
lastLogin: account.lastLoginDate.toISOString(),
password: account.salt,
email: account.username
};
const hash = sha256(JSON.stringify(data), process.env.TOKENSECRET);
if(hash !== req.params.hash) {
writeLog("ERROR", req.url + " - The link is invalid. Err -4");
res.status(500).send( { error: "The link is invalid.", errnum: -4 } );
return;
}
//HERE REDIRECT TO THE CHANGE PASSWORD FORM
});
} catch (err) {
writeLog("ERROR",req.url + " - Unexpected error during the password reset process. " + err.message);
res.status(500).send( { error: "Unexpected error during the password reset process :| " + err.message, errnum: -99 } );
return;
}
});
In my application I'm using passport-local with this Account model
import mongoose from 'mongoose';
import passportLocalMongoose from 'passport-local-mongoose';
const Schema = mongoose.Schema;
let accountSchema = new Schema ({
active: { type: Boolean, default: false },
activationDate: { type: Date },
signupDate: { type: Date },
lastLoginDate: { type: Date },
lastLogoutDate: { type: Date },
salt: {type: String},
hash: {type: String}
});
accountSchema.plugin(passportLocalMongoose); // attach the passport-local-mongoose plugin
module.exports = mongoose.model('Account', accountSchema);
create a random reset key in your DB, persist it with a timestamp. then create a new route that accepts the reset key. verify the timestamp before changing the password to the new password from the route.
never tried this but i ran across this some time ago which is similar to what you need:
https://github.com/substack/node-password-reset
Maybe this article could help:
Password Reset Emails In Your React App Made Easy with Nodemailer
i am not a professional or expert but this worked for me ,well if you are sending password reset email u, while using passport authentication then use
// here User is my model enter code here`
User.findOne({ username: req.body.email }, (err, user) => {
user.setPassword( req.body.password, function(err, users) => {
User.updateOne({ _id: users._id },{ hash: users.hash, salt: users.salt },
(err,result) => {
if (err) {
} else {
}
})
})
})
now here use id to update if you email instead of id in updateOne() it wont work

Response will not return when trying to save model with Mongoose in Restify API

I am building an API using Restify and Mongoose for NodeJS. In the method below after finding the user and verifying their password, I am trying to save some login information before sending the response back to the user. The problem is the response will never return. If I place the response outside and after the save call, the data never gets persisted to MongoDB. Am I doing something wrong? And help would be great as I have been working on this for the past 2 days.
login: function(req, res, next) {
// Get the needed parameters
var email = req.params.email;
var password = req.params.password;
// If the params contain an email and password
if (email && password) {
// Find the user
findUserByEmail(email, function(err, user) {
if (err) {
res.send(new restify.InternalError());
return next();
}
// If we found a user
if (user) {
// Verify the password
user.verifyPassword(password, function(err, isMatch) {
if (err) {
res.send(new restify.InternalError());
return next();
}
// If it is a match
if (isMatch) {
// Update the login info for the user
user.loginCount++;
user.lastLoginAt = user.currentLoginAt;
user.currentLoginAt = moment.utc();
user.lastLoginIP = user.currentLoginIP;
user.currentLoginIP = req.connection.remoteAddress;
user.save(function (err) {
if (err) {
res.send(new restify.InternalError());
return next();
}
// NEVER RETURNS!!!!
// Send back the user
res.send(200, user);
return next();
});
}
else {
res.send(new restify.InvalidCredentialsError("Email and/or password are incorrect."));
return next();
}
});
}
else {
res.send(new restify.InvalidCredentialsError("Email and/or password are incorrect."));
return next();
}
});
}
else {
res.send(new restify.MissingParameterError());
return next();
}
},
One cause of this issue can be if you have a pre save hook which errors silently.
If you find your model as a .pre('save' () => {...}) function then double check this method is reached after you call save, and that it returns without errors.
Documentation on mongoose middleware can be found here

Resources