API Key Authentication in a REST API with JAX-RS
April 15, 2016 | 4 min ReadThe designer of a new REST APIs soon comes across the problem of authentication. He has a number of standardized or custom methods to choose from. In this blog entry I want to outline design and implementation criteria for API Key authentication.
Why API Keys?
Authentication methods for HTTP requests include but are not limited to standardized methods
and non-standard methods:
- Credentials in the request
- API Key authentication
The first 4 methods are designed for human authentication, typically in a browser. On the other hand, REST APIs are often designed for machine to machine communication. The differences between human and machine authentication will become clearer with a more detailed explanation of API Key requirements.
API Key Requirements
An API Key has these properties:
- A token, in the form of a relatively long random string (e.g. 32 characters)
- An identifier, for storage and unique identification
- Transmitted with the request (Warning: this requires SSL for productive usage)
- Known to the client
- Can be validated by the server
- Unique to a device or software
- Bound to a user if necessary
A client device will store the token. In a way this is like a password, but there is no need for restricting this to human memory capabilities. The token is transmitted with every request so the request can always be authenticated.
Because the API key has the same sensitivity as a password, it also should never be stored in the clear in a database or password file. Instead, a hash should be stored with similar (but not quite the same) properties as a password hash. This also serves as identifier. I’ll explain more on that later.
Users and API Keys
In the end every action is executed under some user with some rights. The database entry for the API Key can link to the user under whose name the machine will act.
Requests should be logged, but as with passwords, the token must not end up in a log file. Use the identifier instead. When a client device goes rogue and floods a server with requests or misbehaves otherwise, a single API Key can be revoked without affecting other devices, even other devices of the same user.
Submitting the token with JAX-RS
In this JAX-RS based example the API Key is sent as a custom HTTP Header. By convention custom HTTP headers start with ‘X’.
HTTP sends headers in the cleartext, so this approach requires encryption in the transport layer. For any productive use of this approach HTTPS is required.
The server receives the API Key with the annotation @HeaderParam at the desired parameter.
@Path("/my/resource")
public class AssignLeafletRestService {
@POST @Produces({ MediaType.APPLICATION_JSON })
public String create(@HeaderParam("X-My-API-Key-Token") String token, ...) {
// look up API Key for token
// log API Key usage
// proceed with request
}
The client adds the header when it sends the request.
Entity entity = createEntity();
ClientConfig config = new ClientConfig();
Client client = ClientBuilder.newClient(config);
WebTarget target = client.target(getBaseURI());
Response response = target.path("my").path("resource").request().accept(MediaType.APPLICATION_JSON)
.header("X-My-API-Key-Token", "012345678901234567890123456789ab").post(entity);
From the request/response side this is all there is in regards to the API key. Care must be taken on the server side to securely manage the token.
Creation, Storage and Hashing
The token can be any string, but it obviously must meet some requirements. The main power of an API key over an human chosen password is that it can be truly random, and of a considerable length, e.g. 32 characters. Have a look into UUID generation to receive such strings at low cost.
The token should not be stored in cleartext anywhere on the server. Instead its storage should meet password storage standards. Passwords are hashed with a hashing algorithm that was designed for this purpose such as BCrypt. BCrypt is designed as an expensive task and will stand up some time to brute force attacks.
BCrypt has two inputs, the password and the salt. Depending on the implementation of BCrypt the password is truncated at 56 or 72 bit. A good random salt must be used. The API key should carry both values. A token with 32 random characters can be considered as
- 10 characters for the password
- 22 characters for the salt
This provides a deterministic hash value that is considered state of the art for password hashing.
For identification purposes this hash must also be unique, so a collision detection should be done when generating a token.
tldr; With HTTPS, long random strings and bcrypted hashing, secure machine to machine authentication can be implemented at relatively low cost.