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:
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:
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:
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:
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:
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:
Once I’ve told Passport how to validate API keys, I can then ‘authenticate’ API
requests like so:
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:
Create a user model and persist it somewhere (a database server).
Give users the ability to generate / remove multiple API key pairs.
Setup your OAuth endpoints if you’re using OAuth.
Setup PassportJS and write rules for authenticating users using either HTTP Basic Authentication or Oauth (both use cases require separate setup / configuration).
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:
Having a unified model would also allow you to do interesting things like this:
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.