How To Submit Security Tokens to an API Provider, Pt. 1
There are many blog posts out there about the pros and cons of using a stateful security session tracking mechanism (a cookie, in most cases) versus using a stateless bearer token that’s submitted with each request to the server (the prefered model for apps that use APIs). This post isn’t one of those. Here, I’ll share my thoughts regarding a question I was asked recently by someone at a client regarding how the bearer token should be passed from API consumer to API provider.
In web application SSO, it is typical that a bearer token (e.g. SAML2, JWT, etc.) is used to initially establish a user’s security session on a system, but then a cookie (probably with an opaque value) is used to track the user’s security session. This represents a stateful security session tracking mechanism that was initialized by an SSO protocol.
This post assumes that a bearer token (most likely a JWT token acting as an OAuth2 access token) is cached on the API consumer and passed in every interaction (API call) between the client and server, as is common with modern single page applications (SPAs) and native mobile applications.
The difference between these two approaches is this: statelessness employs a bearer token submitted on every API request, while statefulness involves a cookie to track security session state. One of the goals of the REST architectural style is statelessness; a major focus recently has been on how to achieve stateless security with API consumers for use with SPAs, traditional web applications, or native mobile applications.
The allure of token-based authenticationIn our scenario, the advantages of bearer-token-based authentication include (at the API layer):
- It’s stateless
- It’s scalable
- A bearer token, such as JWT, can be validated locally without an external call to the identity provider (IdP)
- Cross-domain access controls can be enabled using Cross-origin resource sharing (CORS)
- Claims are stored in the token (this enables the statelessness of the security context)
- It’s more flexible
- Expiration functionality is built in
- There’s no need to ask users for “cookie consent” or to deal with blocked cookies
- It works better on mobile devices
Depending on how long the user must stay logged in, extending the validity of the access token may not be recommended. There are important security implications to doing these things that go beyond the scope of this post.
The token hand-offTo start, we will look at the ways that the access token can be passed from API consumer to API provider. To keep things simple, let’s just assume a simple scenario with an API consumer and an API provider (no API gateway). In many real-world SPAs, there’s a dedicated API backend that is built on top of a traditional web application framework that likely still uses a cookie to track a stateful security session.
When an API gateway is placed in front of that backend API and proxies traffic for multiple APIs or is outside the control of the API owner, then the access token passing patterns described here should be used.
- Query parameter
- Form parameter
- Message body field
- HTTP header (let’s assume authorization header)
Form parameters could be used, but they don’t work for every type of HTTP operation (GET, for example). Similarly, not every HTTP operation (like, again, GET) has a message body; so, form parameters are not a good option for a general purpose security token propagation mechanism. So, that’s not a workable solution in the general case.
That leaves us with cookies and HTTP headers (let’s assume the the authorization header is being used).
Should you keep tokens in cookies or in local storage?There are two patterns for client-side storage of bearer tokens: cookies and using HTML5 local storage. If cookies are being used to transmit the bearer token from client to server, then cookies would also be used to store the bearer token on the client side. Likewise, if the authorization header is used to transmit the token, then HTML5 local storage (or session storage) would have to be used to store the bearer token.
Another way of phrasing this discussion is: should the bearer tokens be stored on the client side in cookies or in local storage? The answer to this question will then answer our original question of how best to transmit the token. I’ve heard strong arguments in favor of each, which we will walk through.
There tends to be a misconception that using cookies implies that state must be maintained on the server side. This is not true if all claims needed to recreate the security session are available in a bearer token that is submitted via cookie. For the purpose of this post, I’m assuming that isn’t being done. As a result, there are unique benefits to both headers and cookies/local storage.
In favor of the HTTP authorization header and local storage:
- The relevant specification, RFC 6750, states that bearer tokens (including the OAuth2 access tokens) should be passed between API actors in the authorization HTTP request header (with “bearer “ prepended to the value)
- Local storage cannot be accessed across domains
- Token size (if using JWT) depends on what the browser and server support
- Token size (if using JWT) depends on what the browser and server support.
Image: Flickr Creative Commons/T-eresa