I have implemented some user authentication on a single Heroku dyno using express (node.js)+ mongodb and everything is working fine. However, when I increase the number of dynos (more than 1), I cannot login, I keep being redirected on my login page, meaning my session hasn't been set. Here is my code:
checkCookies = function(req, res, next) {
if(req.session.user){
res.locals.user = req.session.user;
next();
}
else{
res.redirect('/login');
}
};
app.use(express.cookieParser());
app.use(express.session({ secret: '0GBlJZ9EKBt2Zbi2flRPvztczCewBxXK',
cookie: {httpOnly: true, maxAge:14*24*60*60*1000}
}));
What is the best solution to handle shared session on express/node.js using mongodb?
The above answers are misleading in that they imply you can't share cookie based sessions across multiple dynos on Heroku.
I'm able to to use cookie based sessions across multiple dynos if I use cookie-session as opposed to express-session. What's missing from the first post in this thread is the secret value is NOT passed to the cookie parser. This means that node will assign a random hash to the parser each time the process restarts or when a new dyno spins up.
Doing the following works for me:
app.use(express.cookieParser('0GBlJZ9EKBt2Zbi2flRPvztczCewBxXK'))
app.use(express.session({
secret: '0GBlJZ9EKBt2Zbi2flRPvztczCewBxXK',
cookie: { httpOnly: true, maxAge: 14 * 24 * 60 * 60 * 1000 },
}))
connect-mongo should meet your needs: https://github.com/kcbanner/connect-mongo
Use connect-mongo module with express.
var http = require('http'),
express = require('express'),
session = require('connect-mongo')(express)
And then in your workers setup session to store externally. Code below will use session from mongo, cookies and extra headers in order to allow cross-domain and jsonp.
app.configure(function() {
app.use(express.cookieParser());
app.use(express.session({
store: new session({
db: 'sessions'
}),
secret: 'yoursecret',
cookie: {
path: '/',
maxAge: 1000 * 60 * 60 * 24 // 1 day
}
}));
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
next();
});
app.set('jsonp callback', true);
});
For anyone looking for a quick copy/paste solution that is a bit more updated then basically follow the link Dan posted above or just use my snippet below:
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
var mongoose = require('mongoose'); // if using mongoose
mongoose.connect(connectionOptions); // if using mongoose
app.use(cookieParser());
app.use(session({
store: new MongoStore({mongooseConnection: mongoose.connection}),
secret: '0GBlJZ9EKBt2Zbi2flRPvztczCewBxXK',
cookie: {
path: '/',
maxAge: 1000 * 60 * 60 * 24 // 1 day
}
}));
Related
I was making a React project, and I was using Express for backend. I set http://mini-api.moonlab.ga as a virtual host for Express server.
I sent a HTTP Request to express server with Fetch:
fetch("http://mini-api.moonlab.ga/login/", {
credentials: "include"
})
and as I expected there was a CORS error. So I installed cors package, and I set code like this in Node.js:
app.use(cors({
origin: true,
credential: true
}));
And I respond to client from server like this:
app.get("/login", (req, res) => {
const session = req.session;
if (session.miniAccount == undefined) {
session.miniAccount = Math.floor(Math.random() * 1000000);
}
res.writeHead(200, {"Access-Control-Allow-Credentials": true});
res.write(String(session.miniAccount));
res.end();
})
After I did like this, there wasn't any CORS error, but the session don't persist. When I send a request again, the session data keeps changes.
Well how to make session persist?
Server's session code:
app.use(express_session({
secret: secret.app_key,
resave: false,
saveUninitialized: true
}));
You may try setting a maxAge value inside cookie
...
const session = require("express-session");
...
app.use(
session({
secret: secret.app_key,
resave: false,
saveUninitialized: true
cookie: {
maxAge: 3600000 //session expires in 1 hr
}
})
);
I solved it myself by editing package.json.
I added "proxy": "mini-api.moonlab.ga" in package.json.
Than I edited fetch().
previous
fetch("http://mini-api.moonlab.ga/login")
new
fetch("/login")
And it worked.
I would like to disable the session creation for a specific route, as "/app/....";
I tried one way by using the code see below :
pool = mysql.createPool(mysqlOptions);
var sessionConnection = mysql.createConnection(mysqlOptions);
var sessionStore = new MySQLStore(mysqlOptions, sessionConnection);
app.use(function(req, res, next){
if (!req.path.startsWith("/app/"))
session({
key: 'x',
secret: 'x',
store: sessionStore,
resave: false,
saveUninitialized: true,
cookie: {maxAge: moment().endOf('days').diff(moment())} })(req, res, next);
else
next();
});
I have the warning message see bellow and after few minutes, the server is not reachable.
MaxListenersExceededWarning: Possible EventEmitter memory leak
detected. 11 disconnect listeners added to [MySQLStore]. Use
emitter.setMaxListeners() to increase limit
Could somebody explain me ? Thanks in advance.
If you put the route declaration BEFORE your session middleware, then the session will not be created for that specific route handler.
app.use("/app", appRouter); // this will be handled before session is created
app.use(session(...));
If you want to call the session dynamically the way you are in your code, then you should create the session middleware once and then call that dynamically:
pool = mysql.createPool(mysqlOptions);
const sessionConnection = mysql.createConnection(mysqlOptions);
const sessionStore = new MySQLStore(mysqlOptions, sessionConnection);
const sessionMiddleware = session({
key: 'x',
secret: 'x',
store: sessionStore,
resave: false,
saveUninitialized: true,
cookie: {maxAge: moment().endOf('days').diff(moment())}
});
app.use(function(req, res, next){
if (!req.path.startsWith("/app/"))
sessionMiddleware(req, res, next);
else
next();
});
I have this Node API that frontends a backend OAuth server. At the end of the SAML OAuth dance, I set the Bearer Token in a browser cookie.
// need cookieParser middleware before we can do anything with cookies
app.use(express.cookieParser());
// set a cookie
app.use(function (req, res, next) {
// check if client sent cookie
var cookie = req.cookies.cookieName;
if (cookie === undefined)
{
// no: set a new cookie
var randomNumber=Math.random().toString();
randomNumber=randomNumber.substring(2,randomNumber.length);
res.cookie('cookieName',randomNumber, { maxAge: 900000, httpOnly: true });
console.log('cookie created successfully');
}
else
{
// yes, cookie was already present
console.log('cookie exists', cookie);
}
next();
});
app.use(express.static(__dirname + '/public'));
Now I was introduced to a fancy NPM which does pretty much the same thing https://github.com/mozilla/node-client-sessions
While I was almost inclined on using this NPM, I bumped into express-session. https://github.com/expressjs/session - this is for server side sessions. But this also sets a cookie
var express = require('express');
var session = require("express-session");
var app = express();
app.use(session({
resave: true,
saveUninitialized: true,
secret: 'ABC123',
cookie: {
maxAge: 60000
}
}));
app.get("/test", function(req, res) {
req.session.user_agent = req.headers['user-agent'];
res.send("session set");
});
If my need to set only a bearer token in the browser cookie for subsequent API calls, which option should be my choice?
express-session is my go to.
If you look at what it took to accomplish the same thing with the two different methods, I think the answer is clear.
If all you want to do is set a client cookie that will enable the server to correctly authenticate future requests, express-session is awesome.
Here is an example set from another question I answered that uses MongoDB as a backend to store your sessions:
'use strict';
var express = require('express'),
session = require('express-session'),
cookieParser = require('cookie-parser'),
mongoStore = require('connect-mongo')(session),
mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/someDB');
var app = express();
var secret = 'shhh';
app.use(session({
resave: true,
saveUninitialized: true,
secret: secret,
store: new mongoStore({
mongooseConnection: mongoose.connection,
collection: 'sessions' // default
})
}));
// ROUTES, ETC.
var port = 3000;
app.listen(port, function() {
console.log('listening on port ' + port + '.')
});
Currently we are setting up session in the cookie using express-session with passport.js and connect-redis to store the data in redis.
I have multiple node server serving requests. For each request without a session, I am creating a new session. Sometimes, an existing session id is being assigned to a new request. Before creating a unique session, I am checking whether the cookie is there...if it is, then I am not creating a new session. But while doing so, we are seeing that same session id being shared with different client.
How do I know its being same?
First user tries to login, it gives successful login and sets up the session and gives the correct information about the user profile.
Second user tries to login, it gives a successful login but sets the session as the previous i.e. first user's session , hence the second user sees the first user info in the profile section.
Code for the Session implementation:
function sessionImplementation() {
return function (req, res, next) {
if(/ucompany=s%3A/.test(req.headers['cookie'])){
var cookie = req.headers['cookie'].split("ucompany=s%3A");
var zCookie = cookie[1].split(".");
var genid = zCookie[0];
return session({
genid:function () {
return genid;
},
store: redis,
cookie: {
maxAge: new Date(Date.now() + (7 * 24 * 60 * 60 * 1000))
},
secret: 'ucomp123',
resave: false,
name: "ucompany",
saveUninitialized: true
})(req, res, next)
}
return session({
store: redis,
cookie: {
maxAge: new Date(Date.now() + (7 * 24 * 60 * 60 * 1000))
},
secret: 'ucomp123',
resave: false,
name: "ucompany",
saveUninitialized: true
})(req, res, next)
}
}
What is the issue and how can I fix it?
Update 1
As per #robertklep I have modified my code.
var express = require('express');
var session = require('express-session');
var RedisStore = require('connect-redis')(session);
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var passport = require('passport');
var app = express();
app.use(bodyParser.json());// to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ extended: false }));
app.use(compress());
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
//app.use(cookieParser());
var redis = new RedisStore({
host:config.redis.url,
port:config.redis.port,
prefix:'sess-'+new Date().getDate()+'-'+(new Date().getMonth()+1)+'-'+new Date().getFullYear()+':'
});
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({
store: redis,
cookie: {
expires: new Date(Date.now() + (7 * 24 * 60 * 60 * 1000)),
maxAge:7 * 24 * 60 * 60 * 1000
},
secret: 'ucomp123',
resave: false,
name: "ucomapny",
saveUninitialized: true
}));
app.use(passport.initialize());
app.use(passport.session());
There are several issues with your code:
instead of letting express-session handle the session cookies, you're trying to handle them yourself;
you're instantiating the session middleware for every single request, which is a waste of resources and may also cause problems (I haven't dug into express-session deep enough to make any definitive claims on that);
maxAge (for the cookie) should not be a date but a number (the number of milliseconds from now that the cookie should remain valid); you're confusing it with expires, which is used to set a point-in-time;
The regular way of using it looks like this:
var session = require('express-session');
var express = require('express');
var app = express();
...
app.use(session({
store : redis,
cookie : { maxAge : 7 * 24 * 60 * 60 * 1000 },
secret : 'ucomp123',
name : 'ucompany',
resave : false,
saveUninitialized : true
});
where is a genid ?
you have to generate it yourself. Use uuid package
I have written a simple cms in nodejs using expressjs framework. I used passportjs for authentication using twitter. below is my app.configure:
app.configure(function(){
//views
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
//parse request bodies
app.use(express.bodyParser());
app.use(express.methodOverride());
// session support
app.use(express.cookieParser(cfg.cookie_secret));
app.use(express.session({
key : 'whynode',
store : sessionStore,
cookie: {
expires: new Date(Date.now() + 60 * 10000),
maxAge: 60*10000
}
}));
app.use(passport.initialize());
app.use(passport.session());
//pass user data
app.use(function(req, res, next) {
res.locals.req_path = req.path;
res.locals.user = req.user || false;
next();
});
//get routers
app.use(app.router);
//serve asset files
app.use('/assets', express.static(__dirname + '/public'));
});
I used redis for session store. full app.js code can be viewed here full app.js
What I am now experiencing is when I leave app unused for some minutes, session expires and I need to login again. How do we make so that session doesnot timeout for atleast 2-3 hours of inactivity?
Adjust this code:
cookie: {
expires: new Date(Date.now() + 60 * 10000),
maxAge: 60*10000
}
That sets the expiry for your session to 10 minutes. You don't need to use both maxAge or expires, one will suffice (the difference is that expires uses a Date instance and maxAge just means expire X milliseconds from now).
For RedisStore you can set disableTTL to true. Keys will stay in redis until evicted by other means.
var sessionStore = new RedisStore({client: rClinet, disableTTL: true})