I would need to save some token in express session. So, I would need help how to save this token in session object.
Any example would be more helpful.
Also is it a good practice to save such information in session object or do I need to use some persistent storage like redis cache DB.
Yes, you can store a token in the session. This is generally done as follows:
app.use(session({
token : your_token_value
})
}));
Or, as an alternative way:
app.get('/', function(req, res, next) {
var sessData = req.session;
sessData.token = your_token_value;
res.send('Returning with some text');
});
Regarding the storage place. It is a kind of a different layer under the session. The values which you store in the session can be placed in different locations: in the application memory, in memcache, a database or in cookies.
For production you can use Memory Cache. For instance, https://github.com/balor/connect-memcached:
It can be achieved as follows:
app.use(session({
token : your_token_value,
key : 'test',
proxy : 'true',
store : new MemcachedStore({
hosts: ['127.0.0.1:11211'], //this should be where your Memcached server is running
secret: 'memcached-secret-key' // Optionally use transparent encryption for memcache session data
})
}));
Related
I have a basic node.js express app using express-sessions.
Please can someone help with why the sessions are not persisting and why a new session is created for every request.
The app itself is quite large so i have added a reduced case of the important settings below.
const express = require('express');
const session = require('express-session');
const MongoDBStore = require('connect-mongodb-session')(session);
// initialise express
const app = express();
// initialise db session store
const store = new MongoDBStore({
uri: MONGODB_URI,
collection: 'sessions',
// can also set expire here for auto cleanup by mongodb
});
app.use(session({
secret: 'secret password',
resave: false,
saveUninitialized: false,
httpOnly: false,
secure: app.get('env') === 'production',
store,
}));
...routes
in a user login route the app sets the request user to the session and saves the session. it is expected that this req,session.user will persist between pages but it does not. on each page request i can see a new session (or sometimes multiple sessions, 1 for each file request) being created.
UPDATE
TL:DR;
- robots.txt causes issues if not dealt with, set DEBUG env to express-session to troubleshoot
After a lot of hair pulling I've found a solution and some useful troubleshooting tips.
when running your app, run it with debug set to express-session.
so for those of you that are quite new to this like myself, run your app with a command similar to this:
DEBUG=express-session node 'bin/www.js'
or
DEBUG=express-session node app.js
depending on how you have your app entry point setup.
Doing this will print session related log msgs so you can troubleshoot if the cookie is actually getting sent with each request or not. the error messages will look like something this:
express-session fetching 0wgmO1264PsVvqeLqaIIXd6T0ink0zts +34s
express-session session found +49ms
To troubleshoot the issue of multiple requests causing multiple sessions per page load, Add a middleware at the top of your app before any other middleware. this will allow us to see the request URL and troubleshoot which requests may be interfering with our sessions.
// see what requests are being sent and which ones contain cookies
app.use((req, res, next) => {
const { url } = req;
const isCookieSent = req.headers.cookie;
console.log({ url });
console.log({ isCookieSent });
next();
});
from doing this I found out that the culprit was robots.txt file, Apparently the only path that is ignored by default is favicon.ico.
Because this robots.txt path wasn't handled properly, nor was it sending a cookie, it was causing the duplicate requests and also causing the cookies not to persist.
to fix this you either need to handle or ignore this request prior to getting to the session middleware.
i did this using this middleware, once again fairly high up.
app.get('/robots.txt', (req, res) => {
res.type('text/plain');
res.send('User-agent: *\nDisallow: /');
});
I am new to node.js so if there is anyone with more knowledge feel free to chip in with extra info or cleaner ways of solving this problem. Hopefully this saves some of you a lot of hassle!
I am using Nodejs and express to create a web app. But i am finding some difficulty in maintaining session. i can use req.session.userid = userid , but it is not so reliable. if the server goes down for some time and it has to reboot, the session will be lost.. Is there any way to store the session more effectively?
You can either use a database as stated above, or use the in memory store, like redis. Redis is the preferred way to go when handling user session, since its several factors faster then reading from disk.
Additionally, you may want to look into Json Web Token, so you don't have to store sessions at all, rather just keep a reference to the user token in your database (or redis). This will allow you to easily authenticate on mobile. It can also help prevent csrf attacks if you store the token on a users localstorage (rather then cookie)
You can read about them here: https://scotch.io/tutorials/the-ins-and-outs-of-token-based-authentication, https://scotch.io/tutorials/the-anatomy-of-a-json-web-token, https://scotch.io/tutorials/authenticate-a-node-js-api-with-json-web-tokens
I prefer using the npm module called "connect-mongodb-session". It uses mongodb to store all the sessions. Go to your project directory and install "connect-mongodb-session" using
sudo npm install connect-mongodb-session
And add this to your package.json as dependencies. and this is how you can use it..
Sample code...
var express = require('express');
var session = require('express-session');
var MongoDBStore = require('connect-mongodb-session')(session);
var app = express();
var store = new MongoDBStore({
uri: 'mongodb://localhost:27017/connect_mongodb_session_test',
collection: 'mySessions'
});
// Catch errors
store.on('error', function(error) {
assert.ifError(error);
assert.ok(false);
});
app.use(require('express-session')({
secret: 'This is a secret',
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 7 // 1 week
},
store: store
}));
server = app.listen(3000);
And you are good to go.. use req.session when ever you want, and your sesion will be stored save in mongodb.
for example..
app.post("/login",function(req,res){
//validate login
req.session.userid = userid;
})
even if the server has to reboot, your session will not be lost.
From the Passport docs:
In a typical web application, the credentials used to authenticate a
user will only be transmitted during the login request. If
authentication succeeds, a session will be established and maintained
via a cookie set in the user's browser.
Each subsequent request will not contain credentials, but rather the
unique cookie that identifies the session. In order to support login
sessions, Passport will serialize and deserialize user instances to
and from the session.
So, being that the authentication is maintained in the client's browser, I should be able to scale the web servers to > 1 (each server uses same database) and have Passport deserialize user from an ID stored in a cookie regardless if the server has seen the user before. If the server has seen the user before but no cookie exists, I would expect Passport to fail the authentication...but I can't even get that far with n > 1 servers.
With 1 server process, Passport works fine:
The server is more or less verbatim of docs on both servers (after much trials w/ connect references and such):
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);
});
When a user login is successful, right before I return the HTTP request, I log req.user and req.session:
console.log(req.user);
console.log(req.session);
res.send(200, req.user);
// {"cookie":{
// "originalMaxAge":null,
// "expires":null,
// "httpOnly":true,
// "path":"/"
// },
// "passport":{"user":"ID"}}
When the HTTP request returns, the web app requests / and expects the server(s) to set req.user with ID ID from the cookie passed:
passport.deserializeUser(function(req, user_id, cb) {
console.info('Passport deserializing user: '
.concat(user_id));
console.log(req.user);
console.log(req.session);
// ...
// cb(null, user);
});
With 1 server running, this logs as:
Passport deserializing user: ID
undefined
undefined
Immediately after the callback cb(null, user) and before I return the request, I log req.user and req.session and see what I would expect!
Now, with n > 1 servers running, let's say 2, if the web app requests / from server 2 and expects it to set req.user with ID ID from the cookie passed (originally set by logging into server 1), I don't even see the deserialize function called! When I added logging to the beginning of the HTTP route (after middleware):
console.log(req.user);
console.log(req.session);
I see:
undefined
// {"cookie":{
// "originalMaxAge":null,
// "expires":null,
// "httpOnly":true,
// "path":"/"
// },
// "passport":{}}
...which tells me something is up with the cookie, but what? Both server processes are on Heroku behind the same DNS. I have read various discrepancies about needing some "shared session" store for this...but that's what a cookie is at the client's browser level. Please help! Express is the latest 3.X and Passport is 2.1.
As an alternative to having a server side database, you can store the session data in encrypted cookies, such as those used by Mozilla: https://github.com/mozilla/node-client-sessions. That is more scaleable than any server side database but you have to keep the size of the cookie reasonably small. If I had a bit more time I'd write you a demo but for now I'll just put this down as a question to come back to.
The cookie does not contain session data, it contains just the session ID. Using this ID your Express application will get the related session data from it's session store. And since you are not defining a store when calling app.use(express.session({ secret: 'keyboard cat' })); you are using the built-in memory store which is not shared between processes.
So, install any session store that uses a database, eg. connect-mongo for MongoDB.
Passport is great. I now discovered that I have some problem with how it handles sessions.
I must be using it wrong.
All works well for me with login + sessions + user data I store in my database.
However I find that when I move to production environment (cloud on EC2 with multiple servers), I lose the login session each time.
This is now clear to me - probably happens since the session is unique to each server.
So my question is - how do I get around this..
I guess I will need to store my own cookie on the user's browser?
Does this mean that I cannot use express.session at all?
Thanks,
Ilan
OK,
So basically what I was looking for (not sure it would be the same answer for everyone else) was a way to store session data between loadbalanced instances without making a DB call for every page view, which seems excessive to me, since I just need to keep the user signed in to Google/FB.
It seems that the answer I was looking for was the cookie-session middleware
https://github.com/expressjs/cookie-session
This needs to replace the default express.session mechanism which uses MemoryStore. BTW MemoryStore itself gives you a warning when run that it will not scale past a single process, and also that it may cause a memory leak.
Which if I understand correctly is serializing the session data itself into the session cookie (encrypted) instead of just using a session ID in the session cookie.
This seems perfect to me. Obviously I don't expect it to work if you have a lot of session data, since a cookie is limited in size. In my case, I just needed the name, ID and avatar url, so I think this will suffice.
Thanks for everyone who helped.
You need to store your session data in a 'global' area, that is accessible to all your servers. This could be redis or another DB.
Take the example from MEAN.JS. Here they use express-session with a MongoDB storage container (since they are a MEAN stack ; ), via connect-mongo. Their project is super easy to set up, if just for an example.
Code while setting up express is like this:
//top of file
var session = require( 'express-session' )
mongoStore = require( 'connect-mongo' )( {
session: session
} );
//...later in setup
// Express MongoDB session storage
app.use( session( {
saveUninitialized: true,
resave: true,
secret: config.sessionSecret,
store: new mongoStore( {
db: db.connection.db,
collection: config.sessionCollection
} )
} ) );
// use passport session
app.use( passport.initialize() );
app.use( passport.session() );
I need help understanding the concept of sessions for a web application. I am running a Node.js server with Express 3.0.
My goals are to:
Create a session for each user that logs in
Store this session and use it for validating if the user is already logged in (prevent two devices using the same user at the same time) and to limit access to certain pages (by matching session ID to some other data)
I will be using MemoryStore to save the sessions (seems easiest). If the above goals make sense can you provide a thorough explanation of how to achieve them?
Express has nice examples in the github repo. One of them deals with authentication and shows how to attach the user to the req.session object. This is done inside the app.post('/login') route.
To limit access to certain pages add a simple middleware to those routes
function restrict(req, res, next) {
if (req.session.user) {
next();
} else {
req.session.error = 'Access denied!';
res.redirect('/login');
}
}
app.get('/restricted', restrict, function(req, res){
res.send('Wahoo! restricted area, click to logout');
});
As Brandon already mentioned you shouldn't use the MemoryStore in production. Redis is a good alternative. Use connect-redis to access the db. An example config looks like this
var RedisStore = require('connect-redis')(express);
// add this to your app.configure
app.use(express.session({
secret: "kqsdjfmlksdhfhzirzeoibrzecrbzuzefcuercazeafxzeokwdfzeijfxcerig",
store: new RedisStore({ host: 'localhost', port: 3000, client: redis })
}));
Use MemoryStore in express ONLY if you are not creating multiple instances (such as with the cluster module). If you are load balancing across machines, your load balancer will need to use sticky / persistent sessions.
If you meet those requirements, then all you need to do is upon login, once the credentials are validated, set a session variable to indicate logged in, for example:
req.session.loggedIn = true;
If you want to check if a user is logged in, simply check that variable.
if (req.session.loggedIn) {
// user is logged in.
}
else {
// user is not logged in.
}
You mentioned preventing a single user from having sessions more than one session at a time. To achieve that, you may need to store something in a database indicating that the user is logged in. I warn you, this can be dangerous because of stale sessions. For example, what if a user logs in, but never logs out? What if they close their browser window so the session is gone forever?
Express has no concept of an idle session expiration. I have implemented such a thing by storing all sessions in the database along with a last accessed timestamp and then periodically clean up the session data based on the time. But then you need to update your list of who is logged in as well.