How to add "Remember Me" to my app with passport - node.js

I need a "Remember Me" checkbox when logging in like this .
And I add a middleware before using passport
app.use(function(req, res, next) {
if (req.method == 'POST' && req.url == '/login') {
if (req.body.rememberme) {
req.session.cookie.maxAge = 1000 * 60 * 3;
} else {
req.session.cookie.expires = false;
}
}
next();
});
app.use(passport.initialize());
app.use(passport.session());
I can not login when req.body.rememberme is true and the user is remembered when req.body.rememberme is false.
I also tried connect-ensure-login and it still wrong.
and another question: when should I delete the cookies in my database and how?
:)
Other code is exactly the same as the passport guide
route:
app.get('/', passport.authenticate('local', {
failureRedirect: '/login'
}), function(req, res) {
res.redirect('/user/home');
});
app.post('/login', passport.authenticate('local', {
failureRedirect: '/login'
}), function(req, res) {
res.redirect('/user/home');
});
sessions:
passport.serializeUser(function(user, done) {
var CreateAccessToken = function() {
var token = user.GenerateSalt();
User.findOne({
accessToken: token
}, function(err, existingUser) {
if (err)
return done(err);
if (existingUser) {
CreateAccessToken();
} else {
user.set('accessToken', token);
user.save(function(err) {
if (err)
return done(err);
return done(null, user.get('accessToken'));
})
}
});
};
if (user._id)
CreateAccessToken();
});
passport.deserializeUser(function(token, done) {
User.findOne({
accessToken: token
}, function(err, user) {
if (err)
return done(err);
return done(err, user);
});
});
and the strategie:
passport.use(new LocalStrategy(function(userId, password, done) {
User.findOne().or([{
username: userId
}, {
email: userId
}]).exec(function(err, user) {
if (err)
return done(err);
if (!user) {
return done(null, false, {
message: 'Invalid password or username'
});
}
if (user.Authenticate(password)) {
return done(null, user);
} else {
return done(null, false, {
message: 'Invalid password or username'
});
}
});
}));
I noticed that Express will update the cookie only when hash value changed. so I have modified the code in the middleware
app.use(function(req, res, next) {
if (req.method == 'POST' && req.url == '/login') {
if (req.body.rememberme) {
req.session.cookie.maxAge = 1000 * 60 * 3;
req.session._garbage = Date();
req.session.touch();
} else {
req.session.cookie.expires = false;
}
}
next();
});
now I can login with "Remember Me", but it only works on chromium and firefox on Ubuntu. I still can not login with the "Remember Me" checkbox on chrome and firefox on Win7 and Android.
I checked response header when POST to "/login" on chrome on win7 and it had the same "Set-Cookie" field as it on Ubuntu, why it can not work?
Time is out of sync...so I post a extra time field.
$('#login').ajaxForm({
beforeSubmit: function(arr, $form, option) {
arr.push({
name: '__time',
value: (new Date()).toGMTString()
});
}
});
and the "RememberMe" middleware:
app.use(function(req, res, next) {
if (req.method == 'POST' && req.url == '/login') {
if (req.body.rememberme) {
req.session.cookie.maxAge = moment(req.body.__time).add('m', 3) - moment();
req.session._garbage = Date();
req.session.touch();
} else {
req.session.cookie.expires = false;
}
}
next();
});

I had exactly the same problem as you. The following code works for me:
app.post("/login", passport.authenticate('local',
{ failureRedirect: '/login',
failureFlash: true }), function(req, res) {
if (req.body.remember) {
req.session.cookie.maxAge = 30 * 24 * 60 * 60 * 1000; // Cookie expires after 30 days
} else {
req.session.cookie.expires = false; // Cookie expires at end of session
}
res.redirect('/');
});

There is now a Passport strategy for adding Remember Me functionality writen by Jared Hanson.
https://github.com/jaredhanson/passport-remember-me
This works by issuing a unique remember-me token during every session that is consumed at the start of the next (invalidating it for future use.) As a result, this is likely to be a more secure solution.

Ensure that app.use(express.bodyParser()) is placed above your middleware, as you are relying on req.body.rememberme

Related

Passport isAuthenticated() always returns false?

So I am having a problem with Passport I've been trying to move from my original method of authentication because Passport supports other types like Google and GitHub. I'm trying to implement the local authentication and it doesn't seem to be working, even after looking up many articles and they all don't work.
This is at the top of the code:
const cookieExpirationDate = new Date();
cookieExpirationDate.setDate(cookieExpirationDate.getDate() + 7);
app.use(session({
secret: secret_key,
store: sessionStore,
resave: true,
saveUninitialized: true,
cookie: {
httpOnly: true,
sameSite: 'strict',
expires: cookieExpirationDate
}
}));
// PASSPORT //
app.use(passport.initialize());
app.use(passport.session());
passport.use('local', new LocalStrategy({
usernameField: 'username',
passwordField: 'password',
passReqToCallback: true //passback entire req to call back
}, async function (req, username, password, done) {
if (!username || !password) {
return done(null, false, {message: 'Please complete the form!'})
}
const reqBody = {
response: req.body['h-captcha-response'],
secret: captcha_key
}
let axiosResult = await axios.post('https://hcaptcha.com/siteverify', qs.stringify(reqBody), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
if (axiosResult.data.success === true) {
let results = await runQuery('SELECT * FROM accounts WHERE (username = ? OR email = ?)', [username, username])
const forwarded = req.headers['x-forwarded-for']
const ip = forwarded ? forwarded.split(/, /)[0] : req.connection.remoteAddress
if (!results.length) {
let amtLeft = await loginAttempts(ip);
if (amtLeft > 1) {
return done(null, false, {message: `Incorrect Username and/or Password! (${amtLeft} attempt(s) left)`});
} else {
return done(null, false, {message: `You must wait 15 minutes before trying again!`});
}
}
let user = results[0]
let isMatch = await bcrypt.compareSync(password, user.password)
if (!isMatch) {
let amtLeft = await loginAttempts(ip);
if (amtLeft > 1) {
return done(null, false, {message: `Incorrect Username and/or Password! (${amtLeft} attempt(s) left)`});
} else {
return done(null, false, {message: `You must wait 15 minutes before trying again!`});
}
} else {
if (user.activation_code === "activated") {
return done(null, user)
} else {
return done(null, false, {message: 'Check your email for an activation email!'})
}
}
} else {
return done(null, false, {message: `You must complete the captcha!`});
}
}
));
passport.serializeUser(function (user, done) {
done(null, user.id);
});
passport.deserializeUser(async function (usrid, done) {
let results = await runQuery('SELECT * FROM accounts WHERE id = ?', usrid)
done(results[0]);
});
Login API part:
app.post('/login_sys', regularFunctions, function (req, res, next) {
passport.authenticate('local', {failWithError: true}, function (error, user, info) {
if (error) {
return res.status(500).json(error);
}
if (!user) {
return res.status(401).json(info);
}
return res.status(200).send('Success')
})(req, res, next);
})
regularFunctions:
let regularFunctions = [
bodyParser.urlencoded({extended: true}),
bodyParser.json(),
function (req, res, next) {
console.log('Authenticated: ' + req.isAuthenticated())
if (req.isAuthenticated()) {
req.session.loggedin = true;
return next();
} else {
req.session.loggedin = false;
return next();
}
}
]
I need it to return some sort of notification to the client if it fails or succeeds because I have a little pop up that lets them know they are getting redirected if it works and to notify them of their attempts left. The problem is it works and says that it logged in but when I refresh the page it never did.
Ok just found out the answer after searching for a while, I had to use req.login inside the login_sys route.

req.isAuthenticated() is returning true even after wrong password is entered

I am using passport local strategy to authenticate the users, here is my login code:
app.post("/login",function(req,res){
const user = new model({
username:req.body.username,
password:req.body.password,
});
req.login(user, function(err) {
if (err) {
res.render("login",{error: err});
} else {
passport.authenticate("local")(req, res, function() {
res.redirect("/dashboard");
});
}
});
});
Now if I enter an incorrect password then an unauthorized message comes and then if I go to my dashboard route then req.isAuthenticated() is true,
here is my dashboard code:
app.get("/dashboard",function(req,res){
if(req.isAuthenticated()){
//mywork
}
How to solve this problem and how/where to handle that unauthorized message?
passport.use(model.createStrategy());
passport.serializeUser(model.serializeUser());
passport.deserializeUser(model.deserializeUser());
and
app.use(session({
secret: "secret",
resave: false,
saveUninitialized: false,
}));
You're using req.login. Do you know what it does? Here is how you handle you'r issues, first you create a strategy ( obviously you have a user model ).
const User = require('../models/User');
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
/**
* Sign in using Email and Password.
*/
passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
User.findOne({ email: email.toLowerCase() }, (err, user) => {
if (err) { return done(err); }
if (!user) {
return done(null, false, { msg: `Email ${email} not found.` });
}
if (!user.password) {
return done(null, false, { msg: 'Your account was registered using a sign-in provider. To enable password login, sign in using a provider, and then set a password under your user profile.' });
}
user.comparePassword(password, (err, isMatch) => {
if (err) { return done(err); }
if (isMatch) {
return done(null, user);
}
return done(null, false, { msg: 'Invalid email or password.' });
});
});
}));
Then in your controller you can create a login method:
/**
* POST /login
* Sign in using email and password.
*/
exports.postLogin = (req, res, next) => {
const validationErrors = [];
if (!validator.isEmail(req.body.email)) validationErrors.push({ msg: 'Please enter a valid email address.' });
if (validator.isEmpty(req.body.password)) validationErrors.push({ msg: 'Password cannot be blank.' });
if (validationErrors.length) {
req.flash('errors', validationErrors);
return res.redirect('/login');
}
req.body.email = validator.normalizeEmail(req.body.email, { gmail_remove_dots: false });
passport.authenticate('local', (err, user, info) => {
if (err) { return next(err); }
if (!user) {
req.flash('errors', info);
return res.redirect('/login');
}
req.logIn(user, (err) => {
if (err) { return next(err); }
req.flash('success', { msg: 'Success! You are logged in.' });
res.redirect(req.session.returnTo || '/');
});
})(req, res, next);
};
To make sure you'r routes are authenticated:
app.get('/', homeController.index);
app.get('/login', userController.getLogin);
app.post('/login', userController.postLogin);
app.get('/logout', userController.logout);
app.get('/forgot', userController.getForgot);
app.post('/forgot', userController.postForgot);
app.get('/reset/:token', userController.getReset);
app.post('/reset/:token', userController.postReset);
app.get('/signup', userController.getSignup);
app.post('/signup', userController.postSignup);
app.get('/account/verify', passportConfig.isAuthenticated, userController.getVerifyEmail);
app.get('/account/verify/:token', passportConfig.isAuthenticated, userController.getVerifyEmailToken);
app.get('/account', passportConfig.isAuthenticated, userController.getAccount);
And your app settings for passport strategy session:
app.use(session({
resave: true,
saveUninitialized: true,
secret: process.env.SESSION_SECRET,
cookie: { maxAge: 1209600000 }, // two weeks in milliseconds
store: new MongoStore({
url: process.env.MONGODB_URI,
autoReconnect: true,
})
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
app.use((req, res, next) => {
if (req.path === '/api/upload') {
// Multer multipart/form-data handling needs to occur before the Lusca CSRF check.
next();
} else {
lusca.csrf()(req, res, next);
}
});
app.use(lusca.xframe('SAMEORIGIN'));
app.use(lusca.xssProtection(true));
app.disable('x-powered-by');
app.use((req, res, next) => {
res.locals.user = req.user;
next();
});
app.use((req, res, next) => {
// After successful login, redirect back to the intended page
if (!req.user
&& req.path !== '/login'
&& req.path !== '/signup'
&& !req.path.match(/^\/auth/)
&& !req.path.match(/\./)) {
req.session.returnTo = req.originalUrl;
} else if (req.user
&& (req.path === '/account' || req.path.match(/^\/api/))) {
req.session.returnTo = req.originalUrl;
}
next();
});

Not able to solve passport-saml req.isAuthenticated() false issue

I'm new to saml and using Nodejs + Express + passport-saml + okta identity provider. I know this is a duplicate question but somehow I am not able to solve this by looking lot of threads on the internet.
I used yeoman express generator for project. Here are my settings:
Server is behind ngnix using https. So, if I hit https://mywebsite.com, it redirects internally to localhost:3000 on that server.
express.js
var samlUtil = require('./saml-util.js');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
limit: '2mb',
extended: true
}));
app.use(compress());
app.use(cookieParser(config.SERVER_KEYS.SERVER_SECRET));
app.use(express.static(config.root + '/public'));
app.use(methodOverride());
app.use(session({
secret: config.SERVER_KEYS.SERVER_SECRET,
resave: false,
saveUninitialized: true,
cookie: {
expires: false,
secure: true
}
}));
app.use(samlUtil.initialize());
app.use(samlUtil.session());
app.get('/saml/response', samlUtil.protected, function(req, res) {
res.end("Hello " + req.session.passport.user);
});
app.get('/saml/invalid', function(req, res) {
res.end("Authentication failed");
});
app.post('/saml/callback', samlUtil.authenticate('saml', {
failureRedirect: '/saml/response/',
failureFlash: true
}), function(req, res) {
req.session.save(function() {
res.redirect('/saml/response/');
})
});
app.get('/saml/login', samlUtil.authenticate('saml', {
failureRedirect: '/saml/response/',
failureFlash: true
}), function(req, res) {
res.redirect('/saml/response/');
});
saml-util.js
var path = require('path');
var passport = require('passport');
var root = path.normalize(__dirname + '/../..');
var constant = require(root + '/app/util/constants.js');
var config = require(constant.APP_CONFIG_FILE);
var SamlStrategy = require('passport-saml').Strategy;
var users = [];
function findByEmail(email, fn) {
for (var i = 0, len = users.length; i < len; i++) {
var user = users[i];
if (user.email === email) {
return fn(null, user);
}
}
return fn(null, null);
}
// Passport session setup.
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing.
passport.serializeUser(function(user, done) {
console.log('serializing');
done(null, user.email);
});
passport.deserializeUser(function(id, done) {
console.log('de-serializing');
findByEmail(id, function(err, user) {
done(err, user);
});
});
passport.use(new SamlStrategy({
issuer: config.SAML.ISSUER_URL,
path: config.SAML.PATH,
entryPoint: config.SAML.ENTRY_POINT,
cert: config.SAML.CERTIFICATE,
}, function(profile, done) {
console.log('got profile');
console.log(profile);
if (!profile.email) {
return done(new Error("No email found"), null);
}
process.nextTick(function() {
console.log('Finding by email');
findByEmail(profile.email, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
console.log('new user');
users.push(profile);
return done(null, profile);
}
console.log('existing user');
return done(null, user);
})
});
}));
passport.protected = function protected(req, res, next) {
console.log('is isAuthenticated =' + req.isAuthenticated());
if (req.isAuthenticated()) {
return next();
}
res.redirect('/saml/invalid');
};
exports = module.exports = passport;
What is happening:
I can hit the URL: /saml/login
Gets redirected to the okta login page (where I have identity settings)
I login successfully
I'm redirected to the URL: /saml/callback with response:
{issuer:
{ _: 'http://www.okta.com/exkctyzcknbMikNjl0h7',
'$':
{ Format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity',
'xmlns:saml2': 'urn:oasis:names:tc:SAML:2.0:assertion' } },
sessionIndex: '_3acb290873febaf825cd',
nameID: 'ashutosh#myemail.com',
nameIDFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
nameQualifier: undefined,
spNameQualifier: undefined,
firstName: 'Ashutosh',
lastName: 'Pandey',
email: 'ashutosh#myemail.com',
getAssertionXml: [Function] }
In the /saml/callback URL, I can see value returned in req.user but
req.isAuthenticated() in saml-util is always returning false.

restrict login page access if user isLoggedin using node and express

login api:
api.post('/login', function(req, res) {
User.findOne({
username: req.body.username
}).select('password').exec(function(err, user) {
if(err) throw err;
if(!user) {
res.send({ message: "User doesn't exist."});
} else if(user) {
var validPassword = user.comparePassword(req.body.password);
if(!validPassword) {
res.send({ message: "Invalid password."});
} else {
/////// token
var token = createToken(user);
res.json({
success: true,
message: "Successfully logged in.",
token: token
});
}
}
});
});
middleware:
api.use(function(req, res, next) {
console.log("somebody just came to our app.");
var token = req.body.token || req.param('token') || req.headers['x-access-token'];
// check if token exists
if(token) {
jsonwebtoken.verify(token, secretKey, function(err, decoded) {
if(err) {
res.status(403).send({ success: false, message: "failed to authenticate user."});
} else {
req.decoded = decoded;
next();
}
});
} else {
res.status(403).send({ success: false, message: "no token provided."});
}
});
authService:
authFactory.login = function(username, password) {
return $http.post('/api/login', {
username: username,
password: password
})
.success(function(data) {
AuthToken.setToken(data.token);
return data;
})
}
authFactory.isLoggedIn = function() {
if(AuthToken.getToken())
return true;
else
return false;
}
Now, if my user is logged in and he tries to access: localhost:3000/login , then he should be redirected to localhost:3000/
only after he logs out, he should be able to access the login page (similar to facebook).
How to do this?
1
In login API store user info in respond object(res).
2
app.get('/',function(req,res){
if(req.user){
res.redirect('/login');
} else {
res.render('home');
}
});
RECOMMENDED:PASSPORT and its Local-Strategy.
exports.loginPage = (req, res) => {
if(req.session.user){
res.redirect("/");
} else {
res.render("loginPage");
}
};
On your login route, add the logic inside the middleware function. You could the same on the registration page as well.
exports.registrationPage = (req, res) => {
if(req.session.user){
res.redirect("/");
} else {
res.render("registrationPage");
}
};
The key takeaway is this: if you want a user not to see a page on your site, go to the page's route, add the if/else logic.
Hope that helps, cheers!

passport deserializeUser not called in sails

I am using Sails version 0.11 and trying to configure app with jwt authentication.
config/passport.js
var passport = require('passport');
var jwt = require('express-jwt');
module.exports = {
http: {
customMiddleware: function(app) {
console.log('express midleware for passport');
app.use(jwt({ secret: sails.config.session.secret, credentialsRequired: false}).unless({path: ['/login']}));
app.use(passport.initialize());
app.use(passport.session());
}
}
};
services/passport.js
/* other code */
passport.serializeUser(function(user, done) {
console.log(user);
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
console.log(id);
findById(id, function(err, user) {
if (err)
done(err);
else if (!user) {
done(null, false);
}
else
done(null, user);
});
});
AuthController.js
module.exports = {
login: function(req, res) {
passport.authenticate('local', function(err, info, user) {
if (err) {
return res.send(err);
}
else if (!user) {
return res.send(info);
}
else {
var token = jwt.sign(user, sails.config.session.secret, {
expiresInMinutes: 5 // expires in 5 minutes
});
res.json({
success: true,
message: 'Enjoy your token!',
token: token
});
}
})(req, res);
},
me: function(req, res) {
res.send(req.user);
}
}
Why is my desesializedUser function never called? What is wrong in my code?
Do you configure the strategy for passport?
In the passport doc, the below is mentioned.
Before authenticating requests, the strategy (or strategies) used by an application must be configured.

Resources