I use express and express-session middleware to build a website, with session enabled. But some url such as /health-check' and/version-checkdo not need session, especially the/health-check`, which will generate a lot of useless session in db(the project use mongodb).I believe there is a good solution to solve the problem.
The following is a snippets of the session:
var session = require('express-session'),
passport = require('passport');
var app = express();
//other middleware.
// Express MongoDB session storage
app.use(session({
saveUninitialized: true,
resave: true,
secret: config.sessionSecret,
cookie: { maxAge: 2628000000 },
store: new mongoStore({
db: db.connection.db,
collection: config.sessionCollection
})
}));
// use passport session
app.use(passport.initialize());
app.use(passport.session());
//...
I suggest to create own middleware function and put there your session middleware as function, but with conditions e.g:
app.use(function(req, res, next){
if(...some condition...){//for example check url
//do something or nothing
next();//very important or request will freeze
} else {//otherwise run session
session({your options})(req, res, next);
}
});
instead your app.use(session())
in one custom middleware you can put any other middleware to add conditions. But when you want to wrap more then one middleware to single custom middleware be careful on 'next'. It can be use only once in middleware (multiple middlewares use use it multiple time). Then you have to create your own callback 'next'.
Related
Think what I am trying to do should be relatively easy, but I am loosing the thread, and potentially the will to do this.
Setting up a node application using node and express 4. And I use passport for authentication. Followed an absolutely amazing guide by scott.io which did the trick nicely https://scotch.io/tutorials/easy-node-authentication-setup-and-local
And it works a charm. However, I want to separate my routes, because I like keeping things tidy (thats a lie, but I intend to keep the lie living).
My plan was to have four sets of routes.
api (mapped to /api, using the file ./routes/api.js)
index (mapped to /, using the file ./routes/index.js)
auth (mapped to /auth, keeps track of all authentication, callbacks as well as some activator and other bits)
Now to my issue, I need to make the passport available to app (or get api.js and indes.js to be able to call functions in passport.js) and I can't quite figure out how.
My plan was to initiate passport like so:
var passport = require('passport');
app.use(session({secret: 'Not-telling-you)',
saveUninitialized: true,
resave: true
})); // session secret
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
app.use(flash()); // use connect-flash for flash messages stored in session
//Configuring the passports
require('./config/passport')(passport);
That should give me passport available in app
Next to load the route modules
var auth = require('./routes/auth')(app, passport);
var users = require('./routes/users')(app,passport);
var activator = require('./routes/activator')(app,passport);
This SHOULD allow me to access them in the modules?
Map all toutes in app
app.use('/api', api);
app.use('/auth', auth);
app.use('/', index);
And then write the modules as follows (this is a super simple version of auth)
var bodyParser = require('body-parser');
var activator = require('activator');
var express = require('express');
var router = express.Router();
//Lets read the configuration files we need
var activatorCfg = require('../config/activator.js')
var cfgWebPage = require('../config/webpage.js');
//So we can read the headers easily
router.use(bodyParser.json()); // support json encoded bodies
router.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies
//Activating activator, so we can actively activate the actives
activator.init({user: activatorCfg, transport: activatorCfg.smtpUrl , from: activatorCfg.fromEmail, templates: activatorCfg.templatesDir});
router.get('/login', function(req, res) {
res.render('login.ejs', { title: 'Betchanow - Social betting as it should be' , loginUrl: cfgWebPage.loginUrl, trackingID: cfgWebPage.googleTracking.trackingID, message: req.flash('loginMessage') });
});
module.exports=function(app, passport) {
router
}
My problem is that if I do that, express complains that
throw new TypeError('Router.use() requires middleware function but got a
^
TypeError: Router.use() requires middleware function but got a undefined
If I just return the router (skip wrapping it in a function) I end up with a
var search = 1 + req.url.indexOf('?');
^
TypeError: Cannot read property 'indexOf' of undefined
So is there a right, simple or preferably right and simple way of achieving this?
Think the trick would be to pass app and passport (or only passport), think is I need access to either data or functions from passport in all three, and as I was planning to play with ACL as well, wanted to add that to auth to make my life simple as well.
============== EDIT =============
So here is my issue.
If I now do a post to the authentication route (code below)
//Lets load the modules, note the missing passport
var bodyParser = require('body-parser');
var activator = require('activator');
var express = require('express');
var router = express.Router();
//Lets read the configuration files we need
var activatorCfg = require('../config/activator.js')
var cfgWebPage = require('../config/webpage.js');
//So we can read the headers easily
router.use(bodyParser.json()); // support json encoded bodies
router.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies
//Activating activator, so we can actively activate the actives
activator.init({user: activatorCfg, transport: activatorCfg.smtpUrl , from: activatorCfg.fromEmail, templates: activatorCfg.templatesDir});
//Lets start with our routes
// process the login form
router.post('/login', passport.authenticate('local-login', {
successRedirect : '/', // redirect to the secure profile section
failureRedirect : '/login', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
module.exports=function(app, passport) {
return router;
}
I end up with the issue that the route code (./routes/auth.js) have no clue what passport is. (loded in the app as follows):
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
app.use(flash()); // use connect-flash for flash messages stored in session
//Configuring the passports
require('./config/passport')(passport);
You'll be getting the error because you're not returning the router.
module.exports=function(app, passport) {
return router;
}
EDIT:
You won't be able to access the passport property because you're not passing it around or setting it anywhere. As I'm not sure how passport works (whether it acts as a singleton or not), so you have a couple of options in your routes file:
var passport = require('passport')
which may "just work", or
var passport; // at the top of your routes file
// your routes
module.exports = function(app, _passport) {
passport = _passport;
return router;
}
A third option is to wrap your entire routes in the exports method:
// your requires here
module.exports = function(app, passport) {
//So we can read the headers easily
router.use(bodyParser.json()); // support json encoded bodies
router.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies
//Activating activator, so we can actively activate the actives
activator.init({user: activatorCfg, transport: activatorCfg.smtpUrl , from: activatorCfg.fromEmail, templates: activatorCfg.templatesDir});
//Lets start with our routes
// process the login form
router.post('/login', passport.authenticate('local-login', {
successRedirect : '/', // redirect to the secure profile section
failureRedirect : '/login', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
return router;
}
I'm using express 4.0 with module express-session, connect-redis and passport for manage sessions. Everything is ok for login and logout, I can retrieve session etc.
But I've noticed something weird: even when I'm anonymous, if I'm going to redis and type:
$ KEYS *
Redis return an entry 1) "sess:VWdwTjPXkITmqQ77xI8cotlltdrz7S8s" even if nobody is currently connected. When I'm connect, this key is replaced by another corresponding to my session. And when I'm logout, the key changes again by another. When the anonymous user call an URL, my req.sessionID is also set.
In this site https://engineering.linkedin.com/nodejs/blazing-fast-nodejs-10-performance-tips-linkedin-mobile I've read something about create session even for anonymous (7. Go session-free) and I think it's related.
I add the middlewhere in the main app.js file with something like:
var
passport = require('passport'),
User = require('../models/user'),
LocalStrategy = require('passport-local').Strategy,
session = require('express-session'),
RedisStore = require('connect-redis')(session);
app.use(session({
store: new RedisStore(app.locals.services.session.config),
secret: 'mysecretstring'
}));
app.use(passport.initialize());
app.use(passport.session());
passport.use(User.createStrategy());
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
I have the problem even if I start from a fresh server and if I try to GET the homepage where I'm doing nothing:
index: function (req, res) {
res.render('home/index');
}
Even in this case, a new key is created in my redis.
Note:
If I remove both lines, no key are created.
app.use(passport.initialize());
app.use(passport.session());
So, my question is: How to avoid a key creation in Redis for anonymous users ? (and, is it a good idea to not store a session for anonymous ?).
Thanks !
If you don't want a new session to be created for each request, set saveUninitialized to false in the express-session middleware:
app.use(session({
store : new RedisStore(app.locals.services.session.config),
secret : 'mysecretstring',
saveUninitialized : false,
}));
I am using a basic node express-session setup with memory store and I have this code on the server:
app.use(require('express-session')({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: { secure: true }
}));
app.post('/api/logIn', function(req, res) {
req.session.userName = req.body.userName;
}
app.get('/api/getProfile', function(req, res) {
User.findOne({'userName' : req.session.userName},
function (err, userProfile) {
console.log('getProfile executed for user:' + req.session.userName);
if (err) throw err;
console.log(userProfile);
});
});
The problem is that req.session.userName from getProfile route is undefined, although it is not in the previous request to logIn route. I inspected HTTP headers and strangely there are no headers dealing with cookies, from the server or from the client. Right now I have no idea what could be the problem.
You say cookie: { secure: true }, but is your web server actually on a secure connection? If not, then the cookie won't be written.
From the docs:
Please note that secure: true is a recommended option. However, it
requires an https-enabled website, i.e., HTTPS is necessary for secure
cookies. If secure is set, and you access your site over HTTP, the
cookie will not be set.
Also its important to note if you are using fetch() to make your API calls to include { credentials: 'include' } in the options of your fetch() call. Otherwise the cookie will not set properly and your session will not persist. Make sure that on your server side you do something like:
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Credentials', true);
next();
});
so that your headers are set properly and cors wont be an issue. Took me awhile to figure this out but its working now!
The session need is stored in a cookie, so we use this to parse it, some like this:
var cookieParser = require('cookie-parser');
// must use cookieParser before expressSession
app.use(cookieParser());
Full example: http://code.runnable.com/U0tEnHwraXYyp-JG/simple-usage-of-express-session-and-cookie-parser-with-express-for-node-js
I set up session management in my node js/ express js website successfully. I stores session data in mongo db. I want the session to be valid for the users who log in for a couple of weeks. The code is as follows:
var cookieParser = require('cookie-parser');
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
app.use(cookieParser());
app.use(session({
store: new MongoStore({ mongoose_connection: db }),
secret: 'cookie_secret',
cookie: { maxAge: null }
}));
It works fine for normal users, but my problem is with web crawlers such as google bots and facebook bots. I still want them to crawl my website but I don't want their sessions to be stored in my mongo db. It's taking up lots of space and storage is increasing daily which costs me money.
How to selectively choose which sessions to be stored in the db. I can check for req.headers['user-agent'], but where to use it in my code? How to tell express-session not to store session sometimes?
You can use the session middleware conditionally, based on the User-Agent header. A simple example:
var sessionMiddleware = require('express-session')({
...configuration here...
});
app.use(function(req, res, next) {
var ua = req.get('user-agent');
// If the User-Agent header contains the string "Googlebot",
// skip the session middleware.
if (/Googlebot/.test(ua)) {
req.session = {}; // perhaps a bit too simple?
return next();
}
return sessionMiddleware(req, res, next);
});
It would depend on your actual use of req.session if the code above works, or if you need to mock req.session a bit better (for instance, if you use any of the req.session methods in your code,
you may need to mock those too).
http://www.javascriptkit.com/javatutors/cookie.shtml
Session-only cookies, on the other
hand, stores information in the
browser memory, and is available for
the duration of the browser session.
In other words, the data stored inside
a session cookie is available from the
time of storage until the browser is
closed. Moving from page to page
during this time does not erase the
data.
How can I achieve this using Express.js?
First off, that website is a horrible place to go.
Now on to the question.
What sessions actually are:
Data is stored on the server side.
A cookie is issued which contains an ID.
This ID gets send back to the server on every request, due to the fact that the browser sends the cookies.
Now the server can re-associate the ID in the cookie - commonly called Session ID or short SID - with the session data stored on the server.
Express.js has support for sessions built in.
What the example shows:
Setting up the Express.js middleware
Using a third-party store for saving the session data, in this case Redis (which IMO is overkill for your problem atm)
Installing Redis requires quite some work, but it's also possible to use Express.js's built-in memory store:
var express = require('express');
var app = express.createServer();
var MemoryStore = require('connect/middleware/session/memory');
app.use(express.bodyDecoder());
app.use(express.cookieDecoder());
app.use(express.session({ store: new MemoryStore({ reapInterval: 60000 * 10 }) }));
app.get('/', function(req, res){
req.session.visitCount = req.session.visitCount ? req.session.visitCount + 1 : 1;
res.send('You have visited this page ' + req.session.visitCount + ' times');
});
app.listen(4000);
This will simply keep track of how many times you visited the page, closed your browser and re-opend. The counts will still be there.
You can find more on the options of the MemoryStore, like maximum life time of a session, etc. here.
The following is what I wanted (sort of). When I close browser the information is gone.
var express = require('express');
var app = express.createServer();
var MemoryStore = require('connect/middleware/session/memory');
app.use(express.bodyDecoder());
app.use(express.cookieDecoder());
app.get('/remember', function(req, res) {
res.cookie('rememberme', 'yes', { expires: new Date() - 1, httpOnly: true });
});
app.get('/', function(req, res){
res.send('remember: ' + req.cookies.rememberme);
});
app.listen(4000, '127.0.0.1');
app.use(express.session({cookie: { path: '/', httpOnly: true, maxAge: null }, secret:'eeuqram'}));
The above works on IE8, Firefox and Chrome.
The important piece is maxAge:null
app.get('/remember', function(req, res) {
res.cookie('rememberme', 'yes', { expires: 0, httpOnly: true });
});
This will set session cookie. On browser close it will be erased!
Below is the updated code for Alfred's answer (session using Express.js).
var express = require('express');
var app = express.createServer();
var MemoryStore = require('/home/node/node_modules/connect/lib/middleware/session/memory');
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({
key: 'some-key',
secret: 'some-We1rD sEEEEEcret!',
store: new MemoryStore({ reapInterval: 60000 * 10 })
}));
app.get('/', function(req, res) {
req.session.visitCount = req.session.visitCount ? req.session.visitCount + 1 : 1;
res.send('You have visited this page ' + req.session.visitCount + ' times');
});
app.listen(4000);
I know this is an old question but I'm adding an answer since all answers here seem to be either outdated, have security flaws or are just plain wrong.
As of now, express uses the MemoryStore by default, you don't need to explicitly handle that.
Also, as of now, the express-session's official readme page has a stark warning at the beginning to not use MemoryStore as the session store for production, quoting:
Warning The default server-side session storage, MemoryStore, is purposely not designed for a production environment. It will leak memory under most conditions, does not scale past a single process, and is meant for debugging and developing.
For a list of stores, see compatible session stores.
Here's a simple solution with connect-mongodb-session if you want to use MongoDBStore for session storage:
import express from 'express';
import session from 'express-session';
import ConnectMongoDbSession from 'connect-mongodb-session';
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(session({
secret: < COOKIE_SECRET >,
name: 'sessionId', // Don't use the default name, see http://expressjs.com/en/advanced/best-practice-security.html
cookie: {
httpOnly: true,
secure: true, // Remove this if you're not using HTTPS, but it will be a massive security flaw
sameSite: 'strict',
},
store: getStore(),
// Boilerplate options, see:
// * https://www.npmjs.com/package/express-session#resave
// * https://www.npmjs.com/package/express-session#saveuninitialized
resave: true,
saveUninitialized: true,
}));
function getStore() {
const MongoDBStore = ConnectMongoDbSession(session);
const store = new MongoDBStore({
uri: < DATABASE_URI >,
collection: < SESSION_COLLECTION_NAME >,
connectionOptions: {
useNewUrlParser: true,
useUnifiedTopology: true,
},
});
store.on('error', (error: any) => {
console.error(error);
});
return store;
}