Quantcast
Viewing all articles
Browse latest Browse all 278

The Problem with API Authentication in Express

Express has become a popular tool for building REST APIs, which rarely need features that most web frameworks ship with: session and cookie support, templating, etc. Since Express comes with none of these, you can to quickly compose API services without navigating around (or needing to disable) core functionality.

At the same time, API authentication is an increasingly critical part of a developer’s stack. As more developers build APIs they need to provision API keys, audit API requests, and securely authenticate / authorize users against the API.

The problem: There aren’t enough good API authentication tools out there for Express, which leads to security holes and a lot of headaches. This post will walk through some of the challenges – and solutions – in managing your API access in Express.

REST API Security Explained

Before diving into the ecosystem options and challenges that currently exist, I’d like to take a moment to cover how REST API authentication should ideally work. There are exceptions to this rule, of course, but in general, the following hold true for most public (and private) API services.

There are two common ways to secure your REST API service: either via HTTP Basic Authentication or OAuth 2.0 with Bearer Tokens.

If your service is going to transmit sensitive information, it’s best to serve it over HTTPS to ensure that data can’t be leaked in transit.

API Keys

At the core of any API service are API keys. API keys allow developers to authenticate against an API service.

But what should API keys look like, ideally? What is the ideal way to generate API keys?

Well, before answering that question, lets take a look at one way you don’t want to do it.

How Not to Generate API Keys

How many of you have used an API service that generates a single API key for you?

For instance, many times I’ll sign up for an API service and get a generated API key that looks something like this: 6myyAgKSZuLaextotmfQiRPdLkc79ycjgqhqKD51.

Unfortunately, if you’re only receiving a single API key from a provider, chances are, this provider isn’t properly securing their REST API.

All API keys should really be API key pairs.

The way HTTP Basic Authentication works is that it allows you to specify two pieces of information with each request: a ‘username’ and a ‘password’.

When you submit an API request to a service secured with HTTP Basic Authentication, what you’re really doing is taking your username and password (API key pair), smashing them together into a string (separted by a colon character), then base 64 encoding the result and setting it as the HTTP Authorization header.

If you have only a single API key, you’ll end up with an HTTP Authorization header that looks like this: apikey:.

As you can imagine, this can lead to guessable API keys as an attacker can simply try every possible string until they ‘guess’ the correct API key. Depending on the length of your API key, this might make an attacker’s job very easy since an attacker only needs to guess your API key in order to make API requests on your behalf. For instance, let’s say your API key is 5 characters in length — this means an attacker could simply try every combination of characters until they guess your API key.

With two API keys (a username and a password), it is much harder for an attacker to ‘brute force’ your API keys as it will take much, much longer computationally to try that number of string permutations.

If you’re generating API keys in a sequential non-random way, your API will be abused. Using sequentially-numbered IDs can open a potential attack vector known as fusking.

For instance, I’ve personally seen APIs in the past where the API given out to developers is an incrementing number. So for instance, as a developer I might get assigned an API key that looks like this: 123456. As you can imagine, if I were to try 123455, I’d probably be using another account’s API key!

For this reason, even if you’re generating sufficiently random API keys, it’s highly recommended that you provide developers with an API key pair.

API keys should consist of a pair of unique, random numbers, also known as an ID and secret (as a rule of thumb, you should use a globally unique number for both your ID and secret).

In Node, you can generate globally unique API keys using the node-uuid library:

var uuid = require('node-uuid');

var keyPair = {
  id: uuid.v4(),
  secret: uuid.v4(),
};

Because an API key is a pair of two values, you can treat the id as a ‘username’ and the secret as a ‘password’. Assuming the user keeps the secret value secure, and treats it safely (like a password), this makes API keys as safe as username / password pairs, which means we can apply best practice techniques for passwords on API keys, too. This gives your users another layer of protection against brute force attacks, and makes brute forcing an API key pair much more difficult.

Other Mistakes

Another common mistake developers make when building API services is that they only allow a user to have one API key.

While this might sound like a decent idea when building your service, consider a common problem: what happens when a user accidentally leaks their API key and they need to quickly replace it?

What happens if a user has an API key pair compromised and needs to change it in all of their codebase(s)?

In situations like this, it’s impossible for your user to avoid downtime as they’ll have to remove their existing API key, and generate a new one (or, in many cases, contact you for help).

If your user must remove their existing API key before creating a new one, then a user could experience downtime from the moment the user disables their old API key until the moment they generate a new API key and deploy it live to all of their application servers.

Allowing users to generate more than 1 API key pair prevents this situation and enables your users to seamlessly recover from accidents. Once they’ve switched over to a new API key pair — without any downtime — they can safely destroy their original API key pair without effecting service.

Multiple API keys also allows developers to build more complex applications and billing functionality. For instance, tracking service usage via API is an enormous pain if you want to bill each end-user separately. Giving your developers multiple API keys gives them the ability to do smart billing and access control downstream — for their end users.

Basic Authentication

Now that we’ve covered API key best practices, let’s talk about HTTP Basic Authentication.

The way HTTP Basic authentication works is fairly simple: the request Authorization header is set with a value computed as follows:

var encodedData = new Buffer('apiKeyId:apiKeySecret').toString('base64');
var authorizationHeader = 'Basic: ' + encodedData;

This header then gets sent off to the API service, after which point a developer can then decode the credentials and authenticate them against the API key in the database using password hashing best practices.

Let’s take a look at a very basic Express app which simply prints off the HTTP Basic Authentication credentials received:

var express = require('express');
var app = express();
app.get('/', function(req, res) {
  if (!req.headers.authorization) {
    res.json({ error: 'No credentials sent!' });
  } else {
    var encoded = req.headers.authorization.split(' ')[1];
    var decoded = new Buffer(encoded, 'base64').toString('utf8');

    res.json({
      id: decoded.split(':')[0],
      secret: decoded.split(':')[1],
    });
  }
});

app.listen(3000);

In the code above, we’re reading the HTTP Authorization header, then decoding the resulting string, and lastly — extracting the id and secret submitted.

To submit your credentials using HTTP Basic Authencation, let’s take a look at how this works using cURL:

$ curl -v http://localhost:3000
* Adding handle: conn: 0x7fc153803a00
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x7fc153803a00) send_pipe: 1, recv_pipe: 0
* About to connect() to localhost port 3000 (#0)
*   Trying ::1...
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:3000
> Accept: */*
>
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Content-Type: application/json; charset=utf-8
< Content-Length: 32
< ETag: W/"20-1836064455"
< Date: Wed, 06 Aug 2014 20:17:21 GMT
< Connection: keep-alive
<
* Connection #0 to host localhost left intact
{"error":"No credentials sent!"}

Take a look at the HTTP headers we sent to the server. Notice how there is no Authorization header listed?

Now, let’s try again, but this time, we’ll submit our API credentials and see what happens:

$ curl -v --user 'hi:there' http://localhost:3000
* Adding handle: conn: 0x7fec13003a00
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x7fec13003a00) send_pipe: 1, recv_pipe: 0
* About to connect() to localhost port 3000 (#0)
*   Trying ::1...
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 3000 (#0)
* Server auth using Basic with user 'hi'
> GET / HTTP/1.1
> Authorization: Basic aGk6dGhlcmU=
> User-Agent: curl/7.30.0
> Host: localhost:3000
> Accept: */*
>
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Content-Type: application/json; charset=utf-8
< Content-Length: 28
< ETag: W/"1c-59826226"
< Date: Wed, 06 Aug 2014 20:19:16 GMT
< Connection: keep-alive
<
* Connection #0 to host localhost left intact
{"id":"hi","secret":"there"}

As you can see, this time our request included an Authorization header that looked like this: Authorization: Basic aGk6dGhlcmU=. In this case, we successfully sent off our credentials using Basic Auth to our Express server!

By pairing SSL with Basic Authentication, you’re able to provide developers with a simple and reliable way to authenticate against your API service.

If you’re looking for a simple way to do API authentication, HTTP Basic Auth is an option but it comes with well-known security vulnerablities like credentials leakage, log file inspection, etc. If you’re looking for a secure way to do API authentication, then we highly recommend you use either OAuth (more on this below).

OAuth 2.0 with Bearer Tokens

If you care at all about security (and we hope you do) then OAuth 2.0 with bearer tokens is a great choice.

OAuth is a fairly complicated protocol. In exchange for added complexity, however, you give developers more power and control over API resources.

OAuth allows you to build an API where you can not only assign API keys, but also assign temporary access tokens to users.

Anyhow, OAuth is particularly useful to an API service where:

  • Developers will make requests from devices that are not secure: mobile phones, tablets, etc.
  • Developers need special permissions on a temporary basis. For instance, you might need to sometimes allow developers to add items to your backend, and in other cases only allow them to read specific data.

Here’s how OAuth 2.0 works:

  • When developers sign up for your API service, you provision them API key pairs just as you would normally.
  • In order to access your API, developers must first make an HTTP POST request to a special endpoint you create which validates the user’s API key pair, then generates a temporary access token that will expire in a specified amount of time (usually 1 hour).
  • When developers request an access token, they can also request specific permissions. Permissions in OAuth are called ‘scopes’, and typically look something like this ‘read write delete’.
  • When you get the developer’s request for an access token, you can then approve or deny the developer’s requested permissions: you can say “No, you can only have read access.” — granting permissions is up to you.

Once the developer has exchanged their API key pair for an access token, they can then make API requests to your server using only an access token.

The way the token is submitted to the server is simple: a user must set an HTTP Authorization header that looks like: Bearer <token>, where <token> is the access token previously generated.

In the Node.js world, the simplest way to handle OAuth token generation / etc. is via the oauthorize library.

The Problem with Existing Tools

Since we’ve covered the basics about how API authentication typically works: what’s wrong with the existing tools out there? Good question!

There is really only one popular tool that makes building APIs possible in Express: PassportJS. Passport is an excellent tool — it’s a generic middleware app that comes with many third party backends to handle a wide variety of authentication services.

You can use passport-http in your Express app to handle Basic Authentication, and passport-http-oauth to handle OAuth 2.0 authentication.

While not exactly a Passport issue — building API services with Passport is still quite difficult as the API only allows you to authenticate API requests — but doesn’t provide any sort of authorization rules.

For instance, if you wanted to secure a simple API service with passport-http, you’d need to write the following code to instruct Passport on how to validate your API keys:

passport.use(new BasicStrategy(
  function(id, secret, done) {
    // connect to database and query against id / secret
    User.find({ id: id, secret: secret }, function(err, user) {
      if (err) {
        return done(err);
      } else if (!user) {
        return done(null, false);
      }
      return done(null, user);
    }
  }
));

Once I’ve told Passport how to validate API keys, I can then ‘authenticate’ API requests like so:

app.get('/', passport.authenticate('basic', { session: false }), function(req, res) {
  res.json(req.user);
});

Not too bad — however, it’d be nice to have convenience methods that wrap this functionality to reduce typing or allow you to assert more complex user permissions in addition to basic validation.

Currently, in order to assert permissions you’ve got to write code to:

  • Query your user store and grab a user based on API credentials.
  • Handle permissions checking manually.
  • Explicitly tell passport which form of authentication to use (and whether or not to store cookies) with each request.

This becomes quite a bit more complicated when using OAuth — in addition to setting up the Passport middleware and writing access rules, you’ve also got to run your own OAuth token generation endpoints using oauthorize. All in all you’re looking at 100+ lines of code to do even the basics.

While I love PassportJS, it can get very complicated for API developers with clever wrappers.

How it Works Currently

If you’re currently building a REST API with Express and Passport, you’ve got several big tasks to complete before you can get up and running:

  1. Create a user model and persist it somewhere (a database server).
  2. Give users the ability to generate / remove multiple API key pairs.
  3. Setup your OAuth endpoints if you’re using OAuth.
  4. Setup PassportJS and write rules for authenticating users using either HTTP Basic Authentication or Oauth (both use cases require separate setup / configuration).
  5. Add the passport.authenticate middleware into each of your API routes to ensure Passport is successfully authenticating your users.

How Things Should Work in a Perfect World

In a perfect world, it’d be nice to have:

  • A standard user model. This way, all users are created with the same basic information (email / password).
  • A standard permissions model. This way, there is one and only one way to assign user permissions. I’m partial to doing this through user groups, eg. to give a user admin access, put them in a group named ‘admins’.
  • A standard API key model — this way, API keys can be easily created and removed from a user’s account.

If you can assume that all users are the same, all permissions are stored the same way, and all API key pairs are stored the same way — it’s easy to build simpler wrappers around APIs.

In a perfect world, it’d be nice to be able to handle authentication via middleware in a simple way, for example:

// This would allow users to authenticate with either Basic Auth or OAuth
// 2.0 bearer tokens.
app.get('/', blah.ApiAuthenticationRequired, function(req, res) {
  res.json({ user: req.user, permissions: req.permissions });
});

Having a unified model would also allow you to do interesting things like this:

// Only users in the 'admins' group will be able to access this API
// endpoint.
app.get('/', blah.ApiGroupsRequired(['admins']), function(req, res) {
  res.json({ user: req.user });
})

This sort of functionality has been included in larger frameworks for a long time (Django, Rails, etc.) — and makes writing effective API services with these frameworks significantly easier.

Recap and Thoughts

API authentication is a hot topic in the Node world right now — lots of developers are writing API services, and many of these services are not being properly secured.

While using tools like Passport can help you secure your REST API, there’s a lot of room to improve the API security landscape by creating unified user models and associated tools to simplify the work you need to do.

At [Stormpath][], we’ve been working on our own library to help alleviate some of the pain developers currently face in this space. Our new express-stormpath library allows you to easily build out web apps and API services in Express.

It provides a unified user model, permissions model, and API key model — it also lets you easily create API keys, and supports built-in authentication via HTTP Basic Authentication or OAuth 2.0.

While it still has a long way to go, if you do end up checking it out, I’d love to hear your feedback: randall@stormpath.com.


Viewing all articles
Browse latest Browse all 278

Trending Articles