Environment: Node.js, Express, Express-session, Redis, Digital Ocean Droplet
Background: My app allows users to log into and out of an account that they create.
Problem: Perhaps 1% or 2% of the time when a user logs out the session is not destroyed and the user can get back to their myAccount page by simply browsing to it. This only occurs on my Digital Ocean Droplet, never on my local Windows machine. My Windows box is only used for testing and uses the default session store provided with express-session.
I can confirm with redis-cli
that occasionally sessions are not destroyed. For example just now,
$ redis-cli keys "*"
1) "sess:bVpK6dnOaMsF5ybU0fnnTsCXL14Y-fHh"
After logging out I ran redis-cli
again and the session was still there. Although I had been redirected to my home page I could browse to the myAccount page without logging in again. I clicked log out and this time the session was destroyed and I could not browse to myAccount.
I should emphasize that this almost never happens. The session is usually destroyed. Why might this happen?
This is the setup for my droplet.
const session = require('express-session');
const redis = require('redis');
const redisClient = redis.createClient();
const RedisStore = require('connect-redis')(session);
app.set('trust proxy', 'loopback');
app.use(session({
name: process.env.SESSION_NAME,
proxy: true;
resave: true,
rolling: true,
saveUninitialized: true,
secret: process.env.SESSION_SECRET,
store: new RedisStore({ client: redisClient }),
unset: 'destroy',
cookie: {
maxAge: 2 * 60 * 60 * 1000,
sameSite: 'lax',
secure: true
}
}));
This is how I log users out.
router.get('/logout', redirectLogin, middlewareUserAccounts.logout);
exports.logout = wrapAsync(async function (req, res) {
req.session.destroy(function (err) {
if (err) {
throw new Error(err);
} else {
res.clearCookie(process.env.SESSION_NAME);
return res.redirect('/');
}
});
});
exports.redirectLogin = wrapAsync(async function (req, res, next) {
let doUserValuesExist = req.session.hasOwnProperty('userValues');
if (doUserValuesExist === false) return res.redirect('/login');
return next();
});
After many hours of research and testing I discovered the source of my problem.
router.get('/logout', redirectLogin, middlewareUserAccounts.logout);
I used a GET
request instead of a POST
request. Never use GET
to log a client out of a server because GET
may use the cache in the browser instead of hitting the server. In my case /logout
redirected to /
after the session was destroyed. Since /
is in the cache it served that page up without hitting the server and the session was not destroyed. The reason this wasn't a problem on desktop was because it wasn't using the cache for local files. I'm not sure why this problem only occurred maybe 1% or 2% of the time on my Digital Ocean Droplet but that's the way it worked.