Authentication is the process of verifying someone's identity. There are many ways to verify identities over HTTP APIs, the most popular of which will be described below.
When we talk about API authentication, it's important to distinguish its two components:
- the mechanism — the medium utilized by the authentication protocol
- the method — the protocol for authenticating the user
These two work in tandem and sometimes they're indistinguishable, but we'll explain each one separately and show how they work together.
As mentioned above, a mechanism is a medium utilized by the authentication protocol. The architecture of HTTP allows for several authentication mechanisms.
Cookies are pieces of data exchanged between the client and the server through headers. The server sends one or more cookies via a
Set-Cookie response header and then the browser saves them and includes them in subsequent requests to the server in a
Cookie header. The browser can also send cookies to the server without receiving any first. The goal here is to somehow hold the state between server and client, meaning that the server will have a way to maintain a session with the stateless client.
When setting cookies, the server can apply one or more restrictions, like expiration date and domains where they're supposed to be used. You can learn more about how cookies work on MDN.
There are two important cookie attributes you should know about:
Secure- When set, the cookie will only be sent to the server and saved in the browser if the protocol through which they're sent/received is
httpprotocol can't access cookies with that attribute. If you're communicating over
https, make sure to set this attribute.
Note: If you have a cookie based authentication using the
HttpOnly flag is among the safest option for storing sensitive information in a browser, so if you have a JS-based frontend application that will consume your APIs, and you need to keep the secrets really secret, consider using this technique.
HTTP supports sending a standard
Authorization header in requests. The header consists of a type (indicating what kind of a credential it is) and a value separated by whitespace. An example header can look like this:
A security token with the property that any party in possession of the token (a "bearer") can use the token in any way that any other party in possession of it can. When the server generates a token, it should make it opaque (meaning they can't be interpreted by anyone other than the server).
Authorization header is the preferred mechanism if your API is to be used by platforms other than the browser (browsers using it are susceptible to XSS attacks). It's easier to control than cookies outside of browsers, and it doesn't come with security concerns around using query strings (described below). If your API will be consumed by both browsers and mobile platforms, you should provide for both cookies and the
Authorization header as cookies can complicate the implementation on mobile platforms, and JS clients can't store/exchange authorization information securely with the server, other than through a cookie.
Basic auth is the simplest authentication method. It's a type of
Authorization header authentication, where the "type" value is
The query string is the part of the URL used to provide additional parameters. The query string starts after the question mark character (
?) and delimits multiple parameters with ampersands (
&). Each parameter consists of a name and a string value separated by an equal sign (
For example, the URL
example.com?api_key=123&token=abc has two query params:
api_key with the value
token with the value
The query string can be used to transmit keys and tokens in requests to the server, similar to cookies and the
Authorization header. At the first glance, this might seem insecure, but if it's sent over the
https protocol, the query string is encrypted with the rest of the request and it's visible only to the client and the server.
However, there are other concerns when using query strings to transmit sensitive data:
- URLs (including query strings) are visible in server logs (although, you can filter parameters like these in Rails)
- URLs are saved in browser history (when used in navigation)
- you have to include the key/token in every request, but normally you want to reserve the query string for other parameters (like filters, pagination, sorting, etc.)
- XSS attacks
- length constraints (if you want to ensure your apps work on most modern browsers, exceeding 2047 characters isn't suggested)
Consider using query strings for authentication only when other methods are unavailable.
API keys are unique strings used to identify clients. Think of them like normal passwords, and you should also treat them as such.
API keys in most cases aren't used to authenticate the users themselves, but rather external access to user's resources. In those scenarios, users authenticate through other means (like a server-side session) and then they request one or more keys for API access.
Regarding the format of API keys, they usually consist of alphanumeric characters and have a minimum length to prevent brute-force attacks.
If your system allows access via API keys, it should also provide a way to revoke them, in case they're stolen.
Remind users of security measures they can take to decrease the likelihood of a malicious party accessing their keys and resources: - never store an API key in a public place (private repos on Github count as public too) and never embed it directly in code (unless the key is meant to be public) - regenerate API keys periodically - delete a key if you're not using it anymore
One option for generating API keys is
SecureRandom, a Ruby standard library.
Access and refresh tokens
Access tokens and refresh tokens are artifacts used in the OAuth 2.0 authorization protocol. This protocol enables a user to grant permissions for accessing resources from a 3rd party application. It is most widely used for features like Single Sign On (SSO) via a variety of social media platforms (eg: Facebook, Instagram, LinkedIn).
Once the user logs into the authorization server and grants access to their personal data, an
access token is returned to the client server. This token can be used for subsequent communication with the server on behalf of the user. For that reason, it's critical to have security strategies that minimize the risk of compromising access tokens, for example creating access tokens with a short lifespan. The downside of short-lived access tokens is the fact that the client application must prompt the user to log in again, which makes for bad UX. A better solution would be to use
Refresh tokens are artifacts that let the client application refresh an access token without asking the user to log in. They usually livie longer than access tokens, but that also means that if the refresh token is stolen, the malicious user has the power to create new access tokens, so additional security techniques need to be in place, like refresh token rotation or automatic reuse detection.
The JSON Web Token standard is a method for creating the mentioned access tokens or any other artifact used for transmitting information between 2 servers. You can read more about it on jwt.io.
Believe it or not, cookies aren't the answer sometimes. Here are some usual arguments against them:
- file size (only about 4kb of data can be saved into a cookie)
- they are sent with every request, making it slower
- storing wrong data inside a cookie can be insecure (replay attacks)
When you can't store your session data inside a cookie, Rails has other options that are easily configurable:
Two of the most widely used alternatives for storing session data are:
redis_store- toggle storage to be backed by Redis
active_record_store- toggle storage to be backed by ActiveRecord
On reinventing authentication
For whatever reasons, you may feel like building your own authentication solution. Pursue this motivation, but not with the intention to use it in a production environment.
Authentication is a complex beast encompassing many aspects, most of which you won't be aware of until you start building your own solution, and some of which you'll only learn about when someone bypasses its security.
There are megapopular public libraries available for all common authentication methods, which come with the assurance:
- that they have been tested in real, production environments by thousands if not millions of users,
- that they had their implementation examined by security experts,
- that they had suffered and fixed serious security breaches.
If you think your custom solution will live up to those expectations in the time you have to implement it, then feel free to build it. However, there are better and more productive ways to use your time: solving problems which haven't been solved already.