Express-session creates new session every request - node.js

I put my node express server into production. In development, express-session worked fine (it stored session into cookies with MemoryStore). But now it creates a new session ID into MongoStore every time I refresh or make a request. Also, it doesn't create a cookie in the browser anymore (idk if it's a good or a bad thing)
Most StackOverflow queries on this topic tell me to do things that I've already done so no help there
Here is my express and session setup:
const app = express()
const apiPort = process.env.PORT || process.env.API_PORT
app.use(cors({credentials: true, origin: process.env.ORIGIN_URL}))
mongoose.connection.on('error', (err) => {
console.error(err);
console.log('MongoDB connection error. Please make sure MongoDB is running.');
process.exit();
});
const sessionMiddleware = session({
resave: false,
saveUninitialized: false,
secret: process.env.SECRET,
// secure: true,
cookie: {
sameSite: true,
httpOnly: true,
secure: true,
maxAge: 1000 * 60 * 60 * 24 * 30
},
store: MongoStore.create({
mongoUrl: process.env.MONGODB_URI
})
})
app.use(sessionMiddleware);
app.use(passport.initialize());
app.use(passport.session());
app.use(express.static("public"));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json())
app.post('/auth/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) { return next(err); }
if (info) {
return res.status(401).json({error: info.msg})
}
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.status(200).send(user);
});
})(req, res, next);
});
app.get('/auth/logout', (req, res) => {
req.logout()
res.sendStatus(200)
});
app.get('/checkToken', authCheck, (req, res) => {
res.status(200).send({accountType: res.accountType});
})
Additional info that might be handy for this problem: the front-end is on a separate domain, the aforementioned server is on Heroku, and the DB is in MongoDB cloud.

Turns out all I was missing was app.set('trust proxy', 1) in my setup and sameSite: 'none' in cookie: {} in session middleware.

Related

Session-key changes on every request on hosting with GCP

Some minor information about the problem:
The problem does not occur when ran on localhost/develpoment.
Hosted on App engine with PostgresSQl and I can see that it adds session to Postgres Table in GCP.
I have a very weird problem regarding session. I am trying to implement a login for my web app using Steam-auth on the API. However, it only works to sign in when using Google chrome(not incognito mode). I have tried firefox and safari to but it wont work. The reason is that incognito mode and firefox sends different cookies or less cookies on every request.
I at first thought the problem might have been caused by no session store but after implementing connect-pg-simple it didn't fix it. So from what I can tell it must be a setting issue with the session.
I am sending all requests from with 'withCredentials:true'.
import axios from 'axios';
// config
// ----------------------------------------------------------------------
const HOST_API = process.env.NODE_ENV === "production" ? "https://www.norskins-api.com/api/v1/" : "http://localhost:3005/api/v1/";
const axiosInstance = axios.create({
baseURL: HOST_API,
withCredentials: true
});
axiosInstance.interceptors.response.use(
(response) => response,
(error) => Promise.reject((error.response && error.response.data) || 'Something went wrong')
);
export default axiosInstance;
The site is hosted at norskins.com if you wanna inspect network and see the changes in the cookies.
Server.js:
//Over this are just a lot of imports
app.use(cors(corsOptions));
//SESSION SETUP TOOLS
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
//DDOS PROTECTION
app.use(compression())
app.use(helmet())
app.use(limiter);
//SESSION SETTINGS
app.set('trust proxy', true);
app.use(
session({
...sessionSettings, store: new (require('connect-pg-simple')(session))({
pool: pool
}),
})
);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
// Initiate Strategy
passport.use(
new SteamStrategy(
{
returnURL: BACKEND_URL + "/api/auth/steam/return",
realm: BACKEND_URL,
apiKey: "A SECRET", //this is obv correct
},
function (identifier, profile, done) {
process.nextTick(function () {
profile.identifier = identifier;
return done(null, profile);
});
}
)
);
app.use(passport.initialize());
app.use(passport.session());
app.get("/", (req, res) => {
res.send("Welcome to the most clever backend of all time");
});
app.get("/api/v1/user", (req, res) => {
console.log(req.session.steamuser)
if (req.session.steamuser) {
res.send(req.session.steamuser)
}
else {
res.send(false)
}
});
app.get(
"/api/v1/auth/steam",
passport.authenticate("steam", { failureRedirect: "/" }),
function (req, res) {
res.send(req.user);
}
);
app.get(
"/api/auth/steam/return",
passport.authenticate("steam", { failureRedirect: "/" }),
function (req, res) {
logon(req.user);
req.session.steamuser = req.user;
res.redirect(FRONTEND_URL);
}
);
app.post("/api/v1/logout", (req, res) => {
req.session.destroy();
res.status(200).send();
});
app.listen(port, () => {
console.log("Listening, port " + port);
});
Session Settings:
const rateLimit = require('express-rate-limit');
const isProduction = process.env.NODE_ENV === 'production';
const sessionSettings = {
secret: "ThisSuperSecretKeyThatStackWontSee", //obv something else
saveUninitialized: true,
resave: false,
cookie: {
maxAge: 24 * 60 * 60 * 1000, httpOnly: true, secure: isProduction, sameSite: isProduction ? "none" : "lax"
},
name: 'Session_Id',
};
const urlSettings = {
FRONTEND_URL: isProduction ? "https://www.norskins.no" : "http://localhost:3000",
BACKEND_URL: isProduction ? "https://www.norskins-api.com" : "http://localhost:3005"
}
const corsOptions = {
origin: [urlSettings.FRONTEND_URL],
credentials: true, //access-control-allow-credentials:true
methods: ['POST', 'PUT', 'GET', 'OPTIONS', 'HEAD']
};
const limiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1 minute
max: isProduction ? 1000 : 50000, // 5 requests,
});
I have never hosted something on GCP before, but I have no clue why it only works to sing into chrome normal. and why everything else have different session_id on each request.
logon()
async function logon(user) {
const users = await db.query("SELECT * FROM users WHERE id=$1", [user.id]);
if (users.rows.length > 0) {
return;
}
else {
const dateToday = new Date().toISOString().substring(0, 10);
await db.query("INSERT INTO users(id,steam_name,last_updated) values($1,$2,$3) returning *", [user.id, user.displayName, dateToday]);
return;
}
}

Correct session settings for Google cloud express app doesnt work in production using passport

Problem: Everything works fine in development but also in the normal google chrome browser. However when I try incognito and Firefox it sends two different session ids. I cant find a good reason why the session ID changes on the callback URL. I can see that the correct information gets stored, but when a user logs in on firefox the session key is different when the user is on the callback URL than what it is when the user on the home page. The key is however the same everytime I refresh the site but this does not help me as during login the session key is wrong. As I said it works perfect in development.
I have activated withCredentials on the front end.
I am just super confused as it works fine on google chrome but not on anything else.
Here is my code:
Server.js
... //a bunch of imports
app.use(cors(corsOptions));
//SESSION SETUP TOOLS
app.use(cookieParser());
app.use(express.json())
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.json())
//DDOS PROTECTION
app.use(compression())
app.use(helmet())
app.use(limiter);
//SESSION SETTINGS
app.set('trust proxy', true);
app.enable('trust proxy')
const sessionMemStore = () => {
if (isProduction) {
const firestore = new FirestoreStore({
dataset: new Firestore(),
kind: 'express-sessions',
});
return firestore
}
else {
return null
}
};
app.use(
session({
...sessionSettings,
store: sessionMemStore()
})
);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
// Initiate Strategy
passport.use(
new SteamStrategy(
{
returnURL: BACKEND_URL + "/api/auth/steam/return",
realm: BACKEND_URL,
apiKey: "B14DD3E47A70AC859EE73AB2C656CB34",
},
function (identifier, profile, done) {
process.nextTick(function () {
profile.identifier = identifier;
return done(null, profile);
});
}
)
);
app.use(passport.initialize());
app.use(passport.session());
app.get(
"/api/v1/auth/steam",
passport.authenticate("steam", { failureRedirect: FRONTEND_URL + "/tos" }),
function (req, res) {
res.send(req.user);
}
);
app.get(
"/api/auth/steam/return",
passport.authenticate("steam", { failureRedirect: FRONTEND_URL + "/tos" }),
function (req, res) {
logon(req.user);
req.session.steamuser = req.user;
res.redirect(FRONTEND_URL);
}
);
app.listen(port, () => {
console.log("Listening, port " + port);
});
sessionsettings.
const sessionSettings = {
secret: "someRandomKey",
saveUninitialized: true,
resave: false,
cookie: {
maxAge: 24 * 60 * 60 * 1000, httpOnly: true, secure: true, sameSite: isProduction ? "none" : "lax"
},
name: "session"
};

Passport.js cookie not persist so login auth doesn't work even though session has the passport

I'm using the passport.js local strategy.
I was testing it under proxy setting, localhost.
Things worked fine until I prepare to deploy.
I changed the API address to include dotenv and set CORS settings on the server-side.
When trying to login, CORS works fine, OPTIONS and the POST get 200 ok. The client receives the success data. cookie saved in client.
But when auth checking process runs right after Redux "isLoggedin" state is updated(useEffect), req.session doesn't
t have the passport object. So deserializeUser not be called. The session contains other cookie info except for Passport.
This one is only on Firefox(not Chrome): Page will be redirected if the login auth succeeded(it checks right after login redux state changed), but since it's failed, the user stays on the login page still. But if I try to login on the same page again, the cookie start to show the passport object.(in other words, it shows from 2nd attempt). But it doesn't persist because the Redux login state has been changed to true at the first login attempt already.(so Auth checking doesn't occur.)
client:
Axios.post(
`${process.env.REACT_APP_API_URI}/api/users/login`,
loginData,
{ withCredentials: true, }
).then((res) => res.data){
//save isLoggedin to true in Redux
}
// auth check logic starts right after changing isLoggedin. Axios Get to authenticateCheck
server.js
app.use(helmet());
// app.use(express.static('public'));
app.use("/uploads", express.static("uploads"));
// Passport configuration.
require("./utils/passport");
// connect to mongoDB
mongoose
.connect(db.mongoURI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false,
})
.then(() => console.log("mongoDB is connected."))
.catch((err) => console.log(err));
// CORS Middleware
const corsOptions = {
origin: "http://localhost:8000",
optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
credentials: true,
methods: ["POST", "GET", "DELETE", "PUT", "PATCH", "OPTIONS"],
allowedHeaders:
"Origin, X-Requested-With, X-AUTHENTICATION, X-IP, Content-Type, Accept, x-access-token",
};
// app.use(cors(corsOptions));
app.options(/\.*/, cors(corsOptions), function (req, res) {
return res.sendStatus(200);
});
app.all("*", cors(corsOptions), function (req, res, next) {
next();
});
// to get json data
// support parsing of application/json type post data
app.use(express.json());
app.use((req, res, next) => {
req.requestTime = new Date().toISOString();
next();
});
//support parsing of application/x-www-form-urlencoded post data
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
// db session store
const sessionStore = new MongoStore({
mongoUrl: db.mongoURI,
collection: "sessions",
});
// tell app to use cookie
app.use(
session({
secret: process.env.SESSION_SECRET_KEY,
resave: false,
saveUninitialized: false,
store: sessionStore,
cookie: {
httpOnly: true,
secure: false,
sameSite:"none",
maxAge: 24 * 60 * 60 * 1000, // 24 hours
//keys: [process.env.COOKIE_ENCRYPTION_KEY]
},
name: "pm-user",
})
);
// tell passport to make use of cookies to handle authentication
app.use(passport.initialize());
app.use(passport.session());
app.use(compression());
app.use(flash());
app.use((req, res, next) => {
console.log("req.session:", req.session);
// console.log('/////// req: ///////', req);
console.log("////// req.user: ", req.user, " //////");
next();
});
//---------------- END OF MIDDLEWARE ---------------------//
authController:
exports.authenticateCheck = (req, res, next) => {
console.log("req.isAuthenticated():", req.isAuthenticated());
if (req.isAuthenticated()) {
return next();
} else {
return res.json({
isAuth: false,
error: true,
});
}
};
It would be a really big help if you can advise me where to look to fix it.
Thanks.
I found the solution finally.
It was because the session was renewed every time when a new request starts other than a login request.
The solution was, I had to add { withCredentials: true } to every Axios option in my frontend.

Session not saving after login

I have a problem with express-session. When I try to login I have to save my userdata in cookies and after redirect get it on home page. Now after redirect my cookie is clearing and I have default cookie without some data
const authRoute = require('./routes/auth')
mongoose.connect(
process.env.DB_CONNECT,
{ useUnifiedTopology: true, useNewUrlParser: true },
() => {
console.log('Connected to DB')
});
//Middleware
app.use(express.json())
app.use(cors())
app.use(session({
name: 'sid',
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection }),
secret: process.env.SESS_SECRET,
cookie: {
maxAge: 1000 * 60 * 60 * 2,
sameSite: true,
secure: false
}
}))
app.use('/api/user', authRoute)
Routes file
router.get('/login', (req, res) => {
console.log(req.session);
if (req.session.user) {
res.status(200).send(req.session.user)
} else res.status(403).send({ error: 'You need to login first' })
})
router.post('/login', async (req, res) => {
...
req.session.user = { id: user._id, username: user.name, email: user.email }
req.session.save()
//CREATE AND ASSIGN TOKER
const token = jsw.sign({ _id: user._id }, process.env.TOKEN_SECRET)
res.header('auth-toker', token).send(user)
})
Try disabling the Windows Defender or other anti virus software. Those may not allow the connection to go through

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());
...

Resources