node+express+redis persistent session/remember me - node.js

I am developing in node and express. And I am trying to make a remember me login. I am reading a lot of things on the web, but can't make it work. I don't know if there is a receipe, and if there it is, I could't find it.
I was trying with redis and express session. And is working partially.
If a restart node server, or close and reopen chrome. Session is active. So going into "/" will redirect me to "/index.html".
But if I restart the pc, I lost session. So goning into "/" will redirect me to "login"
Here some significant code from my server:
var redisClient = require('redis').createClient();
var RedisStore = require('connect-redis')(express);
app.use(bodyParser());
app.use(cookieParser());
app.use(express.session({
store: new RedisStore({
host: 'localhost',
port: 6379,
db: 0,
cookie: { maxAge: (24*3600*1000*30)}, // 30 Days in ms
client : redisClient
}),
secret: 'seeeecret'
}));
app.get('/', function(req, res, next) {
res.redirect('/index.html');
});
app.post('/login', function(req, res) {
function loginSuccess() {
req.session.regenerate(function() {
req.session.user = req.body.usuario;
res.sendfile('index.html', {root: './static'});
});
}
function loginFailure(errText, errCode) {
console.log("failed to login. "+errCode+": "+errText);
res.redirect('/login');
}
//Imap email login (the user will authenticate with his email, end email's pass)
checkPassword(req.body.usuario, req.body.server, req.body.password, loginSuccess, loginFailure);
});
function restrict(req, res, next) {
if (req.session.user) {
next();
} else {
req.session.error = 'Access denied!';
res.redirect('/login');
}
}

It seems that you have the "cookie" in the wrong place:
app.use(express.session({
cookie: { maxAge: (24*3600*1000*30)}, // <-- where it belongs
store: new RedisStore({
host: 'localhost',
port: 6379,
db: 0,
client : redisClient
}),
secret: 'seeeecret'
}));

Related

Angular with express, session is not persistent. Each request creates a new session

Original Question
I am using passport.js to do authentication in express, when I use req.flash('message', 'message content') in passport strategy, the flashed information is not under the normal session but 'sessions' and when I tried to retrieve the flashed message using req.flash(), it's an empty array.
I printed out the req
, it looks like this:
MemoryStore {
_events:
{ disconnect: [Function: ondisconnect],
connect: [Function: onconnect] },
_eventsCount: 2,
_maxListeners: undefined,
sessions:
{ gzNcx9b8rcWfDtJm03VnNJfhsNW8EJ7B:
'{"cookie":{"originalMaxAge":null,"expires":null,"httpOnly":true,"path":"/"},"flash":{"message":["emails has been taken, choose another one!"]}}' },
generate: [Function] },
sessionID: 'ffSa89VCV0Mj6uKLrEPMAdNMGLR2I5ML',
session:
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true } },
_passport:
Somehow it opens a new session after redirecting to /api/signupFail. Could anyone help me with this?
Here is my middleware setup:
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var logger = require('morgan');
var passport = require('passport');
require('./config/passport')(passport);
var cors = require('cors');
var session = require('express-session');
var flash = require('connect-flash');
var app = express();
var corsOptions = {
origin: 'http://localhost:4200',
optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
};
app.use(cors(corsOptions));
app.use(logger('dev'));
app.use(cookieParser('Thespywhodumpedme'));
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json());
var goalsRoute = require('./routes/goalsRoute');
var userRoute = require('./routes/userRoute');
// required for passport
app.use(flash());
app.use(session({ secret: 'keyboard cat',resave: true, saveUninitialized:true})); // session secret
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
// use connect-flash for flash messages stored in session
app.use(express.static(path.join(__dirname, 'public')));
app.post('/api/signup', passport.authenticate('local-signup', {
successRedirect: '/api/user/suctest',
failureRedirect: '/api/signupFail',
failureFlash: true
}));
app.get('/api/signupFail', (req, res, next) => {
console.log(req.flash('message')); //this is an empty array
res.status(403).send('fail');
})
Here is my strategy setup:
module.exports = function(passport) {
passport.serializeUser((user, done) => {
done(null, user.id);
});
// used to deserialize the user
passport.deserializeUser((id, done) => {
db.User.getUserById(id, (err, result) => {
done(err, result[0]);
});
});
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) {
if(!email || !password ) { return done(null, false, req.flash('message','All fields are required.')); }
var salt = '7fa73b47df808d36c5fe328546ddef8b9011b2c6';
db.User.getUserByEmail(email, function(err, rows){
if (err) {
return done(req.flash('message',err));
}
if(rows.length > 0){
return done(null, false, req.flash('message',"emails has been taken, choose another!"));
}
salt = salt+''+password;
var encPassword = crypto.createHash('sha1').update(salt).digest('hex');
var newUser = {
name: req.body.name,
email: email,
password: encPassword,
sign_up_time: new Date()
}
db.User.addOneUser(newUser, (err, result) => {
db.User.getUserByEmail(email, (err, result) => {
return done(err, result[0]);
})
});
});
}));
};
Update
At first, I thought it has something to do with flash, but then after printing session out, I found that a new session is created after redirecting. I thought it has something to do with the backend setup. Accidentally, I found this problem doesn't exist when I sent the request from postman. That's when I figured out it might have something to do with Angular which is listening on port 4200 while express listening on port 3000. I was sending the request to port 3000 by hardcoding the port number in httpClient. After I set up a proxy that redirects all API call to port 3000. Everything works just fine.
OK, it turns out that it has nothing to do with the backend. Everything works just fine when I sent the request through postman. The problem is with the frontend, I am using Angular 6, Angular is listening on port 4200 while express listening on port 3000. I set up a proxy in Angular that redirects all API call to localhost: 3000 and the session is persistent.

Express.js session lost after about 3min

I use express.js and React. After success login I store user_id in session but after 2-3min session is lost and when I refresh page they log out me.
Here is my server.js
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'host',
user : 'user',
password : 'password',
database : 'database',
pool : { maxConnections: 50, maxIdleTime: 30},
});
connection.connect(function(error){
if(!!error){
console.log('error');
}else{
console.log('Connected');
}
})
//pluginy
var bodyParser = require('body-parser');
var express = require('express');
var session = require('express-session');
var app = express();
//ustwienia
app.use(bodyParser()); //przesylanie danych w req.body
app.set('trust proxy', 1) // trust first proxy
app.use(session({
secret: 'v8bRRj7XVC6Dvp',
saveUninitialized: true,
resave: true,
cookie: {secure: true}
}));
app.use('/static', express.static(__dirname + '/src'));
app.use('/dist', express.static(__dirname + '/dist'));
app.use('/php', express.static(__dirname + '/php'));
app.set('views', __dirname + '/views');
app.set('view engine','pug');
function checkAuth(req, res, next) {
console.log('User id: '+req.session.user_id);
if (!req.session.user_id) {
if(req.route.path=='/'){
res.render("indexNotLogin",{title:''});
}else{
res.send('You are not authorized to view this page');
}
} else {
next();
}
}
//roots
//index
app.get('/', checkAuth, function(req, res){
res.render("index",{title:''});
})
//funkcje
app.get('/funkcje', function(req, res){
res.render("funkcje",{title:''});
})
//trylogin
app.post('/trylogin', function(req, res){
var username = req.body.name;
var password = req.body.password;
connection.query("SELECT * FROM user WHERE username='"+username+"' AND pass='"+password+"' LIMIT 1", function(error, rows,fields){
//callback
if(!!error){
console.log('error in query');
}else{
if(rows.length){
console.log(rows[0].id)
req.session.user_id = rows[0].id;
res.redirect('/');
}else{
res.send('user dont exist')
}
}
})
})
app.listen(3000,'0.0.0.0', function(){
console.log('Port: 3000')
})
after form submit I do function /trylogin and eveything work fine, req.session.user_id = rows[0].id is user_id, but why session is lost so fast ?
You can increase your session time by using maxAge option in session middleware:
app.use(session({
secret: 'v8bRRj7XVC6Dvp',
saveUninitialized: true,
resave: true,
cookie: {maxAge:900000} //here ,15 min session time
}));
I solved this issue by using express-mysql-session package.
//import
const session = require('express-session');
const MySQLStore = require('express-mysql-session')(session);
let options = {
host: process.env.hostNameDB,
port: 3306,
user: process.env.userNameDB,
password: process.env.passwordDB,
database: process.env.databaseName,
expiration: 1000 * 60 * 60 * 24,
clearExpired: true,
checkExpirationInterval: 1000 * 60 * 60 * 24, //per day db cleaning
};
let sessionStore = new MySQLStore(options);
app.use(session({
key: 'session_cookie_name',
secret: 'session_cookie_secret',
store: sessionStore,
resave: false,
saveUninitialized: false,
}));
By using this package you can store your session data properly and then it will delete the data from DB when checkExpirationInterval is crossed.

NodeJS express session expire after page refresh

The session of my nodejs app is expiring every time I refresh the page, after login. It does work fine if I visit different pages but as soon as I refresh the page, the session ends. I've tried a couple of things but none of it seems to work. How can I keep it from expiring even after the page refresh? If I can store session in the database or someplace else to keep it from expiring.
Here are the files
Passport-init.js
var mongoose = require('mongoose');
var User = mongoose.model('user');
var localStrategy = require('passport-local').Strategy;
var bcrypt = require('bcrypt-nodejs');
module.exports = function(passport) {
passport.serializeUser(function(user, done) {
console.log('serializing user:',user.username);
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
if(err) {
done(500,err);
}
console.log('deserializing user:',user.username);
done(err, user);
});
});
passport.use('login', new localStrategy({
passReqToCallback : true
},
function(req, username, password, done) {
User.findOne({'username': username},
function(err, user) {
if(err) {
return done(err);
}
if(!user) {
console.log("UserName or Password Incorrect");
return done(null, false);
}
if(!isValidPassword(user, password)) {
console.log("UserName or Password is Incorrect");
return done(null, false);
}
return done(null, user);
});
}));
passport.use('signup', new localStrategy({
passReqToCallback : true
}, function(req, username, password, done) {
User.findOne({'username': username},
function(err, user) {
if(err) {
console.log("Error in signup");
return done(err);
}
if(user) {
console.log("Username already exist" + username);
return(null, false);
}
else {
var newUser = new User();
newUser.username = username;
newUser.password = createHash(password);
newUser.save(function(err) {
if(err) {
console.log("Error in saving user");
throw err;
}
console.log(newUser.username + ' Registration succesful');
return done(null, newUser);
});
}
});
}));
var isValidPassword = function(user, password) {
return bcrypt.compareSync(password, user.password);
}
var createHash = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(10), null);
}
};
Auth.js
var express = require('express');
var router = express.Router();
module.exports = function(passport) {
router.get('/success', function(req, res) {
res.send({state: 'success', user: req.user ? req.user : null});
});
router.get('/failure', function(req, res) {
res.send({state: 'failure', user: null, message: 'Invalid Username or Password'});
});
router.post('/login', passport.authenticate('login', {
successRedirect: '/auth/success',
failureRedirect: '/auth/failure'
}));
router.post('/signup', passport.authenticate('signup', {
successRedirect: '/auth/success',
failureRedirect: '/auth/failure'
}));
router.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});
return router;
};
Server.js
var express = require('express');
var path = require('path');
var app = express();
var server = require('http').Server(app);
var logger = require('morgan');
var passport = require('passport');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var mongoose = require('mongoose');
var MongoStore = require('connect-mongo')(session);
mongoose.connect("mongodb://localhost:27017/scriptknackData");
require('./models/model');
var api = require('./routes/api');
var auth = require('./routes/auth')(passport);
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(passport.initialize());
app.use(passport.session());
app.use(session({
secret: 'super secret key',
resave: true,
cookie: { maxAge: 60000 },
saveUninitialized: true,
store: new MongoStore({ mongooseConnection: mongoose.connection })
}));
var initpassport = require('./passport-init');
initpassport(passport);
app.use('/api', api);
app.use('/auth', auth);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
var port = process.env.PORT || 3000;
server.listen(port, function() {
console.log("connected");
});
As per express-session documentation
cookie.maxAge
Specifies the number (in milliseconds) to use when calculating the Expires Set-Cookie attribute. This is done by taking the current server time and adding maxAge milliseconds to the value to calculate an Expires datetime. By default, no maximum age is set.
And use express.session() before passport.session() to ensure login session is stored in correct order. passport docs
In your case you have specified maxAge as 60000ms (60sec) only. Try this:
...
app.use(session({
secret: 'super secret key',
resave: true,
cookie: { maxAge: 8*60*60*1000 }, // 8 hours
saveUninitialized: true,
store: new MongoStore({ mongooseConnection: mongoose.connection })
}));
app.use(passport.initialize());
app.use(passport.session());
...
Increase your cookie maxAge value according to your need, it will solve your issue.
I was facing the same issue as you and I got the problem fixed by doing this:
If anyone is having issues, this could probably help to solve it.
app.use(session({
secret: "our-passport-local-strategy-app",
resave: true,
saveUninitialized: true,
cookie: {
maxAge: 24 * 60 * 60 * 1000
},
store: new MongoStore({
mongooseConnection: mongoose.connection,
ttl: 24 * 60 * 60 // Keeps session open for 1 day
})
}));
I had this problem and I found out how to fix it. In my case, this problem was just during using localhost during running react app on own port. I use the build production version, there was no problem. But it is not good to run build every time you need to see changes.
First I run Nodejs on 5000 port at localhost.
In React's package.json, I added "proxy": "http://localhost:5000/". After that, I ran react app on port 3000. Now when I use fetch, the URL to my API is not http://localhost:5000/api/login but just /api/login.
You can read more about that here:
https://create-react-app.dev/docs/proxying-api-requests-in-development/
Do not forget to remove the proxy from package.json when you will deploy to the server. This is good only for the development version.
As per the fine manual (emphasis mine):
Note that enabling session support is entirely optional, though it is recommended for most applications. If enabled, be sure to use express.session() before passport.session() to ensure that the login session is restored in the correct order.
In your case, the order is not correct. Try this:
...
app.use(session({
secret: 'super secret key',
resave: true,
cookie: { maxAge: 60000 },
saveUninitialized: true,
store: new MongoStore({ mongooseConnection: mongoose.connection })
}));
app.use(passport.initialize());
app.use(passport.session());
...

express-session has different results based on browser. Why?

I'm reading https://www.npmjs.com/package/express-session and am in a world of confusion.
The store I'm using is https://www.npmjs.com/package/express-mysql-session
var options = {
host: 'localhost',
user: 'root',
password: 'somepass',
database: 'somedb'
};
var sessionStore = new SessionStore(options);
app.use(session({
genid: function(req) {;
return genuuid(function(err, uuid){
return uuid;
});
},
key: 'session_cookie_name',
secret: 'session_cookie_secret',
store: sessionStore,
resave: true,
saveUninitialized: true
}));
Consider this route
router.get('/generateUser', function (req, res, next) {
if (req.session.user == null) {
req.session.user = "d";
} else {
req.session.user += "1";
}
res.render('index', {title: 'Express', userId: req.session.user});
});
On a normal chrome browser, each visit to /generateUser is seeing the previous user value. So 5 refreshes and I have a user value of "d11111". But now if I do this incognito or on safari the value is always "d". What are these browsers doing differently?

Nodejs + Passport.js + Redis: how to store sessions in Redis

I've read this topic Node.js + express.js + passport.js : stay authenticated between server restart and I need exactly the same thing, but for Redis. I used such code:
var RedisStore = require('connect-redis')(express);
app.use(express.session({
secret: "my secret",
store: new RedisStore,
cookie: { secure: true, maxAge:86400000 }
}));
And it doesn't work. To connect Redis I use connect-redis module. What I'm doing wrong? Thanks!
UPD: I don't get any errors. To ensure auth processes succesfully, I added log-line, and it executes.
function(email, password, done) {
// asynchronous verification, for effect...
process.nextTick(function() {
findByEmail(email, function(err, user) {
if (!user) {
return done(null, false, {
message: 'Unknown user ' + email
});
}
if (user.password != password) {
return done(null, false, {
message: 'Invalid password'
});
}
//just logging that eveything seems fine
console.log("STATUS: User " + email + " authentificated succesfully");
return done(null, user);
})
});
}));
Log with express.logger() enabled was:
127.0.0.1 - - [Fri, 19 Oct 2012 05:49:09 GMT] "GET /ico/favicon.ico HTTP/1.1" 404 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4"
STATUS: User admin authentificated succesfully
I do suppose that there is nothing wrong with auth/users/credentials/serializing/deserializing itself. The problem is just passport cannot set cookie to Redis and the read it.
I should use
cookie: { secure: false, maxAge:86400000 }
try this out, instead of passing express to const RedisStore pass session.
const redis = require('redis');
const session = require('express-session');
const redisStore = require('connect-redis')(session);
const cookieParser = require('cookie-parser');
const app = require('../app');
app.app.use(cookieParser("secret"));
const rediscli = redis.createClient();
app.app.use(session({
secret: 'secret',
store: new redisStore({
host: '127.0.0.1',
port: 6379,
client: rediscli,
ttl: 260
}),
saveUninitialized: false,
resave: false
}));
What happens when you set the store explicitly? i.e. something along these lines in your app:
var redis = require('redis');
// This is host and port-dependent, obviously
var redisClient= redis.createClient(6379, 'localhost');
app.use(express.session({
secret: 'your secret',
/* set up your cookie how you want */
cookie: { maxAge: ... },
store: new (require('express-sessions'))({
storage: 'redis',
instance: redisClient
})
}));

Resources