Yesterday, I tried to make a website, using Node.js and the framework Express.js. On this website, the user needs to log, with credentials, who are checked in database. If the credentials are correct, I want to save the user's informations in a session.
In this aim, I use the middleware Express-Session, but I have a problem. When the user types good credentials, its informations are correctly stored in the session. However, after redirecting the user to the homepage, the session is cleared, so the variable who stores the user's informations is now undefined.
I tried many solutions, and I searched a lot, but I didn't reached to fix this problem...
There is my code : app.js :
const createError = require('http-errors');
const express = require('express');
const session = require('express-session');
const path = require('path');
const helmet = require('helmet');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const bodyParser = require('body-parser');
const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');
const admRouter = require('./routes/adm');
const urlencodedParser = bodyParser.urlencoded({extended : false});
const app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(urlencodedParser);
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({secret: 'secret', resave: false, saveUninitialized: false, cookie: { maxAge : 60000, secure: false }}));
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/adm', admRouter);
// On utilise helmet pour sécuriser l'application.
app.use(helmet());
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
app.listen(80);
And index.js :
const express = require('express');
const router = express.Router();
const bodyParser = require('body-parser');
const verif = require('../functions/verif');
const password = require('node-php-password');
const dbServer = require('../database');
// const credentials = require('../functions/dbCredentials');
// const dbServer = mysql.createConnection(credentials);
const urlencodedParser = bodyParser.urlencoded({extended : false});
/* GET home page. */
router.get('/', function(req, res, next) {
console.log(JSON.stringify(req.session.user));
res.render('index', {verif, req });
});
router.post('/login', urlencodedParser, (req, res, next) => {
if(req.body.username !== undefined && req.body.password !== undefined) {
if(req.body.username !== null && req.body.password !== null) {
dbServer.query('SELECT * FROM users WHERE username = ?', [req.body.username], function(error, result, fields) {
if (error) throw error;
// if(password.verify(req.body.password, result))
console.log("resultat : " + JSON.stringify(result));
if(result.length > 0) {
const utilisateur = result[0]; // On stocke la ligne concernant l'utilisateur dans une constante locale.
console.log("L'utilisateur existe.");
// On teste le résultat obtenu, pour savoir si son mot de passe est correct.
if(password.verify(req.body.password, utilisateur.password)) {
console.log("Mot de passe correct.");
req.session.user = utilisateur;
console.log(req.session.user);
} else {
// TODO : Session, pour afficher l'erreur.
console.log("Mot de passe incorrect.");
}
}
else {
console.log("L'utilisateur n'existe pas.")
// TODO : Session, pour afficher l'erreur.
}
});
}
}
res.redirect('/');
});
module.exports = router;
With this code, when the user logs in, the :
console.log(req.session.user);
displays correct informations, but the line :
console.log(JSON.stringify(req.session.user));
for the route '/' displays "undefined".
So, I'm a bit lost in this situations... Do you have ideas to fix this problem ? Thanks by advance =)
Yesterday, I tried to make a website, using Node.js and the framework Express.js. On this website, the user needs to log, with credentials, who are checked in database. If the credentials are correct, I want to save the user's informations in a session.
In this aim, I use the middleware Express-Session, but I have a problem. When the user types good credentials, its informations are correctly stored in the session. However, after redirecting the user to the homepage, the session is cleared, so the variable who stores the user's informations is now undefined.
I tried many solutions, and I searched a lot, but I didn't reached to fix this problem...
There is my code : app.js :
const createError = require('http-errors');
const express = require('express');
const session = require('express-session');
const path = require('path');
const helmet = require('helmet');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const bodyParser = require('body-parser');
const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');
const admRouter = require('./routes/adm');
const urlencodedParser = bodyParser.urlencoded({extended : false});
const app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(urlencodedParser);
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({secret: 'secret', resave: false, saveUninitialized: false, cookie: { maxAge : 60000, secure: false }}));
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/adm', admRouter);
// On utilise helmet pour sécuriser l'application.
app.use(helmet());
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
app.listen(80);
And index.js :
const express = require('express');
const router = express.Router();
const bodyParser = require('body-parser');
const verif = require('../functions/verif');
const password = require('node-php-password');
const dbServer = require('../database');
// const credentials = require('../functions/dbCredentials');
// const dbServer = mysql.createConnection(credentials);
const urlencodedParser = bodyParser.urlencoded({extended : false});
/* GET home page. */
router.get('/', function(req, res, next) {
console.log(JSON.stringify(req.session.user));
res.render('index', {verif, req });
});
router.post('/login', urlencodedParser, (req, res, next) => {
if(req.body.username !== undefined && req.body.password !== undefined) {
if(req.body.username !== null && req.body.password !== null) {
dbServer.query('SELECT * FROM users WHERE username = ?', [req.body.username], function(error, result, fields) {
if (error) throw error;
// if(password.verify(req.body.password, result))
console.log("resultat : " + JSON.stringify(result));
if(result.length > 0) {
const utilisateur = result[0]; // On stocke la ligne concernant l'utilisateur dans une constante locale.
console.log("L'utilisateur existe.");
// On teste le résultat obtenu, pour savoir si son mot de passe est correct.
if(password.verify(req.body.password, utilisateur.password)) {
console.log("Mot de passe correct.");
req.session.user = utilisateur;
console.log(req.session.user);
} else {
// TODO : Session, pour afficher l'erreur.
console.log("Mot de passe incorrect.");
}
}
else {
console.log("L'utilisateur n'existe pas.")
// TODO : Session, pour afficher l'erreur.
}
});
}
}
res.redirect('/');
});
module.exports = router;
With this code, when the user logs in, the :
console.log(req.session.user);
displays correct informations, but the line :
console.log(JSON.stringify(req.session.user));
for the route '/' displays "undefined".
So, I'm a bit lost in this situations... Do you have ideas to fix this problem ? Thanks by advance =)
Share Improve this question asked Mar 25, 2018 at 12:55 Yan BuatoisYan Buatois 711 silver badge3 bronze badges 3-
I don't know if this is the only issue, but your redirect is happening before you've done all the session stuff. Depending upon your session settings, you may also have to
.save()
the session changes. – jfriend00 Commented Mar 25, 2018 at 12:58 - Thanks, buteven if i delete the redirection, the problem stay here ^^ And I tried the .save(), but it doesn't have any effect... – Yan Buatois Commented Mar 25, 2018 at 13:02
- Your symptoms sure sound like you didn't successfully save the changes to your session object. – jfriend00 Commented Mar 25, 2018 at 13:06
5 Answers
Reset to default 4I have encountered this problem and solved it through a hint given by the express-session readme.
If you will look at the https://github./expressjs/session#cookiesecure section of readme.md then you will notice this statement:
Please note that secure: true is a remended option. However, it requires an https-enabled website, i.e., HTTPS is necessary for secure cookies. If secure is set, and you access your site over HTTP, the cookie will not be set.
Thus, I attempted the following:
const sess = {
secret: 'My app',
store: MongoStore.create({
mongoUrl: 'mongodb://localhost:27017/myBaseApp',
crypto: {
secret: 'My database'
}
}),
resave: true,
saveUninitialized: false,
cookie: {
secure: false, //setting this false for http connections
maxAge: 3600000,
expires: new Date(Date.now() + 3600000)
}
}
And the redirect started working fine.
The problem is, you are redirecting them way too early. DB queries take much longer than most code ran in a server, which is why callbacks and promises are used. I would move the res.redirect('/')
right at the end of your database query. Right after the last else statement. This should fix it as it allows everything to run before a redirection is made.
You're using the implicit in memory store for express-session
. They remend that this should not be used in production, however my experience with it is that it is peltely unstable even in simple dev environments. I tried redirecting in a timeout setTimeout(() => res.redirect('/'), 1000)
and statistically it failed in less the amount of cases then if I had called it directly, though this is far form ideal.
In my current case I ended up implementing my own dumb memory store just to move things forward and will later switch to a database backend one.
LATER EDIT: I did some extensive testing and it seems that in my case there might be some racing conditions between setting the session cookie and redirecting, I think sometimes the browser redirects before it gets to set the sid thing. I managed to find this out by logging the active sesions at each step during my actions, and it seems new sessions are being created on redirects for no apparent reason.
Almost everything that has a callback is asyncronous and this requires your script to wait for the reply before it sends information/continues other behavior that could create a so called race-condition.
As suggested by other answers, here is an example how to redirect on success/failure:
router.post('/login', urlencodedParser, (req, res) => {
// only in some cases
let sendRedirectDirectly = true;
if(req.body.username !== undefined && req.body.password !== undefined) {
if(req.body.username !== null && req.body.password !== null) {
// here you need to wait for the result of the query
sendRedirectDirectly = false;
dbServer.query('SELECT * FROM users WHERE username = ?', [req.body.username], function(error, result, fields) {
if (error) throw error;
// if(password.verify(req.body.password, result))
console.log("resultat : " + JSON.stringify(result));
if(result.length > 0) {
const utilisateur = result[0]; // On stocke la ligne concernant l'utilisateur dans une constante locale.
console.log("L'utilisateur existe.");
// On teste le résultat obtenu, pour savoir si son mot de passe est correct.
if(password.verify(req.body.password, utilisateur.password)) {
console.log("Mot de passe correct.");
req.session.user = utilisateur;
console.log(req.session.user);
// now we need to call next() or forward the user again
res.redirect('/?loginSuccess');
} else {
// TODO : Session, pour afficher l'erreur.
console.log("Mot de passe incorrect.");
// if password is incorrect
res.redirect('/?incorrectPass');
}
}
else {
console.log("L'utilisateur n'existe pas.")
// TODO : Session, pour afficher l'erreur.
// if username not found:
res.redirect('/?incorrectCredentials');
}
});
}
}
// as noted above
if (sendRedirectDirectly)
res.redirect('/');
});
To allow a better understanding, some packages have a better async
support and would allow writing code like in the example below. (the mssql package for example)
The async
/await
functions/statements pause the execution of your script until the Promise
you await
is fulfilled and throw catchable errors which can be handled by try/catch-block.
If a function gets declared with async
it always returns a Promise
.
You could also wrap the mysql-query into a Promise:
function mysqlQuery(...args) {
return new Promise((resolve, reject) => {
dbServer.query(...args, (error, result, fields) => {
if (error)
return reject(error);
resolve([result, fields]);
})
})
}
Now you can use:
router.post('/login', urlencodedParser, async (req, res) => {
// add some checks for null/undefined
try {
let [ result, fields ] = await mysqlQuery('SELECT * FROM users WHERE username = ?', [req.body.username]);
if (result.length) {
// test the password?
} else {
// invalid username
}
} catch (e) {
console.error(e);
}
// with async behavior you can send it always at the end
res.redirect('/');
})
To overe secure cookie problems in non https hosts, the secure cookie configuration can be set using some environemnt variable, i.e. NODE_ENV
app.use(session({
secret: SESSION_KEY,
resave: false,
saveUninitialized: true,
proxy: NODE_ENV !== "development",
cookie: { secure: NODE_ENV !== "development" }
}))