12

I'm building an API and trying to figure out Authentication in a number of contexts.

Sessions and Password Authentication

The API needs to serve client-side applications we create and deploy, and handle authenticated requests using a password. It's not a great idea to send the password with every request, so it makes more sense to first hit a login endpoint and get a session id. The webapp in question is written in AngularJS, and should track its own session in localStorage to mitigate session hijacking and remove the dependency on cookies to track sessions.

The webapp would need to send the session identifier with every request, and currently does so in the body of the request. This fragments pretty easily and becomes tightly coupled with the API. I would much rather pass all authentication information in one way—through a header, preferably—instead of in a number of different fields spread across the request body, url and headers.

Session Storage

Redis is awesome, of course. Session storage is straightforward and automatically garbage-collects. Unfortunately, sessions in redis are difficult to manage: I cannot easily revoke all sessions for a given user. Adding that capability by storing sessions in a true redis-datastructure instead of the global keyspace removes the ability to add keyed TTLs. My current solution is to store a list of sessions in the MongoDB user collection, and garbage-collect expired sessions on session activity (login/logout, for instance).

The sessions are stored in redis with the connect-redis module, but as they are sent with every request aren't included in the cookies. At the moment, I have a small piece of middleware that takes the session identifier out of the request body and puts it into a req.cookies object.

var express = require('express');
var RedisStore = require('connect-redis')(require('connect'));

var app = express();
app.use(function(req, res, next) {
  req.cookies = {session: req.body.session};
});
app.use(express.session({
  store: new RedisStore({
    client: redisClient,
    prefix: 'session:'
  }),
  key: 'session',
  secret: 'all mine'
});

This approach works fine, except that express.session ends up setting the cookie when express responds, and that's not the desired behavior.

How do I set this up properly in the context of Express?

API Keys

Our API should also support API keys for third-party applications to gain limited and controlled access to our system. The most common mechanism I know of to do this is to distribute API keys to interested developers, and have the developer pass the API key in as part of the request. This runs into the same dilemma that session/password authentication runs into: every API expects the API key in a different part of the request, from the body to the url to the headers.

Expansion and Open Standards

While we do not plan on supporting Open authentication standards like OpenAuth and OpenID at initial release, we do wish to create a framework in which the addition of said standards is straightforward. Part of that could be to unify how authorization credentials are handed to the API, as with session/password and API Key backed authentication.

Another question asks whether customizing the HTTP Authorization header is a good idea, or if a custom header is a better idea.

CRUD Support

In order to support the CRUD paradigm for RESTful APIs, it makes sense to not provide authentication information in the body as that would limit all API requests to POST requests, whereas CRUD recommends use of a variety of HTTP methods.

TLDR

Two things:

  1. How can we use a module like connect-redis without using cookie-based sessions.
  2. How should we configure authentication information for maximum flexibility and interoperability?
Community
  • 1
  • 1
skeggse
  • 6,103
  • 11
  • 57
  • 81
  • "It's not a great idea to send the password with every request" - not necessarily true, you can trust TLS with the password and use HTTP basic auth, as long as all requests are https. So a request would look like `https://username:password@yourdomain.com/api/path?query=string`; and your route handler would test the credentials and load user data and/or session data as appropriate – Plato Oct 15 '13 at 18:36
  • I could absolutely do that. And you're right, TLS/SSL should be sufficient. That said, I'd rather not keep the password longer than I have to anywhere, and I would need to store the password in the webapp for that approach to work. I would much rather convert the username/password into a session token I can store locally. Then, no matter what happens, the only potential point of vulnerability for the password outside of the server is between when the user enters their password and the server receives the login request. – skeggse Oct 15 '13 at 18:51
  • Furthermore, I'm trying to unify how credentials are supplied, and embedding all credentials in the URL seems restrictive, and potentially not compatible with OAuth and the like. – skeggse Oct 15 '13 at 18:52
  • rgr, that's sound logic; i used the basic auth technique in a mobile app, storing the password on the device behind the scenes where I could be reasonably sure that it wouldn't be compromised – Plato Oct 15 '13 at 18:53
  • In some cases reasonably sure isn't enough. It might make sense for a small app, but in our case the password grants access to lots of personal information and the ability to make purchases with saved cards. Thanks for your input, though, I hadn't considered that option. – skeggse Oct 15 '13 at 18:55
  • "should track its own session in localStorage to mitigate session hijacking" - how does this help, exactly? You're still sending the token on each request, so you need TLS/SSL to avoid session hijacking. "and remove the dependency on cookies to track sessions" - why? Is there something wrong with cookies? – Aaron Dufour Jan 04 '14 at 19:04
  • take a look at [an answer](http://stackoverflow.com/questions/20558178/keeping-a-user-logged-in-with-nodejs/20888764#20888764). 1. Either you find a module for your purpose or you modify the module or you make your own session tracking. –  Jan 05 '14 at 17:45
  • Hello @skeggse, I have a requirement that's very close to yours, but I need to use 3rd party services (like Facebook). How do you solved this issue? – gustavohenke Feb 18 '15 at 22:49

2 Answers2

8

As given, the question is unanswerable. A session is state. Therefore you cannot implement sessions statelessly.

If you actually want stateless authentication, then you can't have sessions, and you should use the HTTP Authentication mechanism.

If you actually want sessions, but you don't want to transmit a state token in the Cookie header, then you should use an OAuth mechanism, which allows use of the Authorization header or a request parameter to hold the negotiated state token.

we do wish to create a framework in which the addition of said standards is straightforward

The easiest and best way to do that is to use them to start with. Don't re-invent the wheel. OAuth2 is designed to be easily implemented in multiple use-cases and has its own extension mechanism should you need more.

Community
  • 1
  • 1
OrangeDog
  • 36,653
  • 12
  • 122
  • 207
  • You can have something akin to sessions which is stateless, similar to harun's answer. The solution is sometimes referred to as client sessions, and uses an HMAC to embed a timestamp and server secret in an opaque "session." Also, the statement "OAuth2 is designed to be easily implemented" feels kinda weird to me. I mean, it is sorta designed to be easily implemented, if you have a library for it. If you're trying to use OAuth2 to allow authorizations via several external services, the inconsistencies can be disruptive. – skeggse Aug 21 '15 at 22:20
  • That's not OAuth2, that's OpenID. – OrangeDog Aug 22 '15 at 07:32
  • No, OAuth does not have anything to say about how identity is established. That's why OpenID exists as a layer on top of OAuth. – OrangeDog Aug 22 '15 at 07:38
  • OAuth may not have been designed so, but it can (and has) been used for authentication. – skeggse Aug 22 '15 at 07:39
  • Actually I think we're saying the same thing. OpenID Connect is a small enough extension that I was counting it as a "use" of OAuth2. Sorry for the confusion. – skeggse Aug 22 '15 at 07:42
2

You can consider not using sessions at all by having a token based approach.

You can generate an encrypted token with a hash containing at least user id and expiration date and server secret. You don't even need to include password once you verify the user on login request.

The token would be passed around in the Authorization header, avoiding the use of cookies or any other request parameter.

With this token you can identify the user and check the expiration date without needing a store on serverside, so no session state. You would only need a store if you need to expire authenticated tokens on demand.

harun
  • 1,889
  • 18
  • 19
  • Were I still working on this project, I guess my question would be "what format should the `Authorization` header take?" – skeggse Aug 21 '15 at 22:19