I have an API that can be called either using a browser where requests are transactional and have a session OR directly, eg. using curl, where requests are atomic. Browser requests must first authenticate and then use an express session (connect.sid) for subsequent authorization, direct API calls use a header: Authorization: "SOMETOKEN"
which has to be sent for every request.
The problem I have is, because I'm using the same web server to serve both atomic and transactional traffic, every API call is needlessly being given a session by Express. Every response includes a Set-Cookie and all these sessions are filling up my session store. Therefore: how can I prevent Express from entering a new sess key in the memory store (Redis) when a request contains an Authorization header?
Note. I get that a more classic approach would be to have a separate API server and a separate WEB server but why not run both on one machine? To me, the difference is that the API serves data and the WEB serves views but beyond that they are both part of the same application. I just happen to also allow users to access their data directly and don't force them to use my interface.
Express Config
module.exports = function(app, exp, sessionStore, cookieParser, passport, flash) { app.configure(function(){ // Templates app.set('views', ERNEST.root + '/server/views'); app.set('view engine', 'jade'); app.set('view options', { doctype : 'html', pretty : true }); // Allow large files to be uploaded (default limit is 100mb) app.use(exp.limit('1000mb')); // Faux putting and deleting app.use(exp.methodOverride()); // Static content app.use(exp.static(ERNEST.root + '/server')); app.use(exp.static(ERNEST.root + '/public')); // Handle favicon app.use(exp.favicon()); // For uploads app.use(exp.bodyParser({keepExtensions: true})); // Configure cookie parsing if ( cookieParser ) app.use(cookieParser); else app.use(exp.cookieParser()); // Where to store the session var session_options = { 'secret': "and she put them on the mantlepiece" }; if ( sessionStore ) session_options.store = sessionStore; app.use(exp.session( session_options )); // Rememberance app.use( function (req, res, next) { if ( req.method == 'POST' && req.url == '/authenticate' ) { if ( req.body.rememberme === 'on' ) { req.session.cookie.maxAge = 2592000000; // 30*24*60*60*1000 Rememeber 'me' for 30 days } else { req.session.cookie.expires = false; } } next(); }); // PassportJS if ( passport ){ app.use(flash()); app.use(passport.initialize()); app.use(passport.session()); } }); };
An example route
app.get('/status/past_week', MID.ensureAuthenticated, MID.markStart, function(req, res) { WEB.getStatus('week', function(err, statuses){ if ( err ) res.send(500, err); else res.send(200, statuses); }); });
Authorization Middleware
MID.ensureAuthenticated = function(req, res, next) { if ( req.isAuthenticated() ) return next(); else { isAuthorised(req, function(err, authorised){ if ( err ) return res.redirect('/'); else if ( authorised ) return next(); else return res.redirect('/'); }); } function isAuthorised(req, callback){ var authHeader = req.headers.authorization; if ( authHeader ) { // Has header, verify it var unencoded = new Buffer(authHeader, 'base64').toString(); var formatted = unencoded.toString().trim(); ACCOUNT.verifyAuth(formatted, callback); // verifyAuth callbacks next() when successful } else callback(null, false); // No Authorised header } };