I am looking for guidance on setting up session based authentication with with Express-Session, connect-mongo, and Mongoose. Currently it's just generating a new UUID with every request and not saving anything to the sessions collection. Am I missing something obvious?
index.js
const mongoose = require("./db/connection");
const express = require("express");
const cors = require('cors')
const session = require('express-session')
const MongoStore = require("connect-mongo");
const app = express();
const { v4: uuidv4 } = require('uuid');
//Register .env file
require('dotenv').config()
//Middleware
app.use(express.json());
app.use(session({
genid: (req) => {
return uuidv4()
},
secret: process.env.EXPRESS_SESSION_SECRET,
resave: true,
saveUninitialized: false,
cookie: { maxAge: 24 * 60 * 60 * 1000 },
store: MongoStore.create({
client: mongoose.connection.getClient(),
dbName: process.env.MONGO_DB_NAME,
collectionName: "sessions",
stringify: false,
autoRemove: "interval",
autoRemoveInterval: 1
})
})
);
connection.js
const mongoose = require("mongoose");
require('dotenv').config()
mongoose.connect(`mongodb://devroot:devroot#localhost:27017/${process.env.MONGO_DB_NAME}?authSource=admin`, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true
});
mongoose.connection
.on("open", () => console.log("The goose is open"))
.on("close", () => console.log("The goose is closed"))
.on("error", (error) => {
console.log(error);
process.exit();
})
module.exports = mongoose;
The setting saveUninitialized: false means that a session is established only if it contains some information, that is, if a statement like req.session.attribute = "value" is executed during request processing. If that does not happen, the session is not stored, and also no session cookie issued, so that the next request triggers a new session (with a new UUID), but which may again not be stored.
The author probably "solved" the issue by setting saveUninitialized: true, but this has the following consequences:
Every visitor to the website creates a new session entry (without any information in it) in the database even if they never interact with the site nor log on.
Every visitor gets a session cookie in their browser even before actually logging on.
I consider both these consequences undesirable and would therefore prefer saveUninitialized: false so that sessions without information are effectively not created.
Posting for visibility; this was related to:
saveUninitialized: false
Changing this to true forces save to the store.
Related
I have created a test app, my react app is deployed at vercel and my node express is deployed at render.com. I set the same domain on both to solve cross-site cookie problems (app.mydomain.online)(api.mydomain.online). Now no error is showing when I view the cookie in the header but still when I check the cookie storage it is still not stored or not being saved at the browser's cookie storage.
server is created via npm init.
react is created via npm create-react-app.
as of now this is my sample code.
server
const express = require("express");
const cors = require("cors");
const session = require('express-session');
const app = express();
require('dotenv').config();
const PORT = process.env.PORT;
app.use(express.json());
app.use(cors({
origin: 'https://app.myDomain.online',
methods: ["POST", "PUT", "GET", "OPTIONS", "HEAD"],
credentials: true
}));
const oneDay = 1000 * 60 * 60 * 24;
app.set('trust proxy', 1) // trust first proxy
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: { secure: true, sameSite: 'none' }
}));
app.get('/createSession', (req, res) => {
req.session.user = 'user';
res.send('new session is created');
});
app.get('/', (req, res) => {
res.send('get sess')
});
app.get('/getSession', (req, res) => {
if(req.session.user){
res.send('active');
}else{
res.send('not active');
}
});
app.listen(PORT, () => {
console.log(`The server is running on port ${PORT}`);
});
react
import React from 'react'
import { useEffect } from 'react';
import Axios from 'axios';
function Test() {
useEffect(() => {
Axios.get(' https://api.myDomain.online/createSession',
{ withCredentials: true }
);
}, []);
return (
<div>Test</div>
)
}
export default Test;
From the documentation for express-session...
cookie.expires
Specifies the Date object to be the value for the Expires Set-Cookie attribute. By default, no expiration is set, and most clients will consider this a “non-persistent cookie” and will delete it on a condition like exiting a web browser application.
The docs go on to prefer the maxAge property to control this. Choose a time frame that makes sense for your application. For example, 1 week...
app.use(
session({
secret: "keyboard cat",
resave: false,
saveUninitialized: true,
cookie: { secure: true, sameSite: "none", maxAge: 7 * 24 * 60 * 60 * 1000 },
})
);
I have already fixed it a few days ago, I found out that the reason why the cookie was blocked was that the cookie has exactly the same domain as the server or probably has the same link address. As the domain of the server is api.myDomian.online, the cookie domain can't be api.myDomain.online. Not sure if that is the direct reason, but somewhat similar I think as the code works by setting a new domain to the cookie. I just removed the subdomain of the cookie like .myDomain.online and it works. here is my configuration. My backend is already deployed at aws when I test it but it could also work in render. I will try it out later on.
This is my new configuration
const oneDay = 1000 * 60 * 60 * 24;
const APP_SESSION = session({
secret: 'secrete',
resave: false,
saveUninitialized: true,
name: 'session',
cookie: {
secure: true,
sameSite: 'none'
maxAge: oneDay,
domain: '.domain.com'
}
});
Haven't tried to remove sameSite if it will still work.
Hello I am trying to use express sessions in my backend deployed to firebase cloud functions and I trying to use express-sessions to save data related to the current session. but this does not work, I save something using req.session.value = 5 and I try to get that value later and it is undefined.
this is my current express session config.
const session = require('express-session')
const config = require('./config')
const { v4: uuidv4 } = require('uuid');
admin.initializeApp();
const api = require("./api/index");
const app = express();
//app.use(cookieParser());
//app.set('trust proxy',1)
const sess = {
secret: config.secretKey,
genid: function(req){
return uuidv4();
},
resave: true,
saveUninitialized: true,
cookie: {
sameSite: false
}
}
if(app.get('env')==='production'){
app.set('trust proxy',1)
sess.cookie.secure = true;
}
app.use(session(sess));
app.use(cors());
app.use(express.json());
and I have a middleware that sets the value based in some conditions
// mymdileware
module.exports = (req, res,next)=>{
if(/* conditions */ ){
req.session.value = 5
next()
}
// other code here with a res.send()
})
and an endpoint to get the value of the session, and gets executed after the middleware
app.get("/someEndpontToReadSession", mymdileware,(req, res)=>{
if(req.session.value===5){
// do something and return something to the user
}
// other code here with a res.json()
})
the problem is when I read req.session.value it is undefined, even though I set it in the middleware.
this works locally but It does not work when I deployed to firebase functions.
Edit: I thought the problem was related to that I am not providing a store to the session config, so I added it.
but I am still facing this problem : /
const session = require('express-session')
const config = require('./config')
const { v4: uuidv4 } = require('uuid');
const {Firestore} = require('#google-cloud/firestore');
const {FirestoreStore} = require('#google-cloud/connect-firestore');
admin.initializeApp();
const api = require("./api/index");
const app = express();
//app.use(cookieParser());
//app.set('trust proxy',1)
const sess = {
secret: config.secretKey,
genid: function(req){
return uuidv4();
},
resave: false,
saveUninitialized: true,
cookie: {
sameSite: false
}
}
if(app.get('env')==='production'){
console.log('production : )');
app.set('trust proxy',1)
sess.store = new FirestoreStore({
dataset: new Firestore(),
kind: 'express-sessions'
})
sess.cookie.secure = true;
}
app.use(session(sess));
Firebase Cloud functions dont allow severals cookies Firebase Doc for their CDN cache , they allow just the cookie named __session so :
app.use(session({ name: "__session",
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: {maxAge: 24 * 60 * 60 * 1000}, // 24 hours
store: sessionStorage}));
I faced the issue with express-session and I have tried to solve it.
Here is what I found some solutions.
https://github.com/expressjs/session/issues/633
But honestly to say it doesn't work for me.
When I run a server on my local machine everything works fine and the cookie set and contained in the browser.
But I have no idea why after deploying on the Heroku platform the cookie option not set and not contained in the browser.
Configuration
import {DB_HOST} from "./configs/database";
import {PORT} from "./configs";
import session from "express-session";
import mongoDBSession from 'connect-mongodb-session'
//Session db
const DBSessions = mongoDBSession(session)
//Create express app
const app = express();
app.use('/public', express.static('public'))
//Config Object to Avoid Deprecation Warnings
const dbOptions = {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true
};
//MongoDB
//Connection database
mongoose.connect(DB_HOST, dbOptions);
//Store Connection Object
const db = mongoose.connection;
//Connection events
db.once("open", () => {
console.log("Connected to MongoDB database...");
}).on("error", (err: string) => {
console.log(err);
});
//Store session
const sessionStore = new DBSessions({
uri: DB_HOST,
collection: 'sessions'
})
//https://github.com/expressjs/session/issues/633
app.set('trust proxy', 1);
app.use(routesArray,
session({
secret: SESSION,
resave: false,
saveUninitialized: false,
store: sessionStore,
cookie: {
sameSite: false,
maxAge: MAX_AGE,
secure: false,
httpOnly: true
}
})
)
//Starting server
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
I have a PassportJS authentication set up on my app with strategies for Facebook, Twitter, and Google, along with local. Here's what my authentication route currently looks like:
// /routes/auth-routes.js
import connectRedis from 'connect-redis';
import express from 'express';
import session from 'express-session';
import uuidv4 from 'uuid/v4';
import facebook from './auth-providers/facebook';
import google from './auth-providers/google';
import local from './auth-providers/local';
import twitter from './auth-providers/twitter';
const RedisStore = connectRedis(session);
const router = express.Router();
router.use(session({
name: process.env.SESSION_COOKIE,
genid: () => uuidv4(),
cookie: {
httpOnly: true,
sameSite: 'strict',
},
secret: process.env.SESSION_SECRET,
store: new RedisStore({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
ttl: 1 * 24 * 60 * 60, // In seconds
}),
saveUninitialized: false,
resave: false,
}));
// Social auth routes
router.use('/google', google);
router.use('/twitter', twitter);
router.use('/facebook', facebook);
router.use('/local', local);
// Logout
router.get('/logout', (req, res) => {
req.logout();
const cookieKeys = Object.keys(req.cookies);
if(cookieKeys.includes(process.env.USER_REMEMBER_COOKIE)) {
console.log('REMEMBER COOKIE EXISTS!');
const rememberCookie = process.env.USER_REMEMBER_COOKIE;
const sessionCookie = process.env.SESSION_COOKIE;
cookieKeys.forEach((cookie) => {
if(cookie !== rememberCookie && cookie !== sessionCookie) res.clearCookie(cookie);
});
res.redirect(req.query.callback);
} else {
console.log('NO REMEMBER');
req.session.destroy(() => {
cookieKeys.forEach((cookie) => {
res.clearCookie(cookie);
});
res.redirect(req.query.callback);
});
}
});
module.exports = router;
As apparent, I'm using Redis to store session cookies, which are then sent to the server along with all others cookies upon each page-reload. Here's my question:
Is this enough? Shouldn't I be validating the integrity of received session cookie by looking it up against the Redis store? But if I do that on every page load, won't that affect performance adversely? What's the standard way to handle this?
the repo is up at https://github.com/amitschandillia/proost/blob/master/web.
It look like you're missing part of the integration with PassportJS with express-session. Like the comment mentioned, express-session will store your session data in the redis store, and its key will be stored in the cookie sent to the user.
But you're logging in your users through passportJS, you need to connect these two middlewares together. From the passportJS docs :
http://www.passportjs.org/docs/configure/
Middleware
In a Connect or Express-based application, passport.initialize() middleware is required to initialize Passport. If your application uses persistent login sessions, passport.session() middleware must also be used.
app.configure(function() {
app.use(express.static('public'));
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.session({ secret: 'keyboard cat' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
});
My express app uses PassportJS for storing auth session but it creates a ridiculous number of keys in the redis store and all the keys look the same:
"{\"cookie\":{\"originalMaxAge\":null,\"expires\":null,\"httpOnly\":true,\"path\":\"/\"},\"passport\":{}}"
They do have a TTL but the store is growing in size for no good reason. Any idea why these are being created? My keys looks like:
"sess:8R3A-k6dARJvxXFdAXr5nTG7MeC7JTxb"
"sess:s4VYC-k-nmfSf7n-qGQJimFmt30EYNDp"
"sess:BS7WO92Nyl5R0wAbJ-Vo9o8w1apu0kp7"
"sess:0B1AKS6-MCclPvOXV0nlvNio8U8fxyQO"
"sess:v0UWf60LMwKmMVZgo4RWumX313yPsiD0"
If I just eyeball it, roughly around 10 keys are being created every second or two.
This is how my session code looks:
var express = require('express'),
....
session = require('express-session'),
redisStore = require('connect-redis')(session);
...
app.use(express.static(path.resolve('./public')));
//Redis
var redisClient = redisHelper.init();
app.use(session({
secret: '...',
store: new redisStore({
client: redisClient,
ttl: 86400
}),
resave: false,
saveUninitialized: false,
cookie:{maxAge:86400}
}));
//Passport
app.use(passport.initialize());
app.use(passport.session());
...
Redis init function returns an instance of a redis client:
exports.init = function () {
redisClient = redis.createClient(config.redis.port, config.redis.server, {});
redisClient.auth(config.redis.auth);
redisClient.on('error', function (error) {
//TODO: log the error
winston.error('Error while talking to Redis', {message: error});
});
return redisClient;
};