Every now and then technologies that initially appear to be distinct end up converging on a common approach from opposite directions. I believe that something like that is happening right now in approaches to web and API authentication around the use of tokens and cookies.
Since very early on, the dominant approach to authentication in web applications has been based on the use of session cookies. After logging in, a website would set a secure cookie so that you don’t need to authenticate again until the session expires or you log out. But cookies are vulnerable to CSRF attacks, and to prevent this people started requiring anti-CSRF tokens on any request that caused an action. In a traditional web application these tokens would be generated by the server and automatically added as a hidden field to HTML forms. In a typical JSON API, where the server doesn’t directly generate forms, the token is instead often communicated via headers instead. But the principle is the same: the cookie alone is no longer sufficient to grant access, you need both the cookie and the CSRF token. The cookie identifies the user, but it grants no authority unless accompanied by the CSRF token.
Many APIs now do away with cookies altogether and rely solely on a token, such as an OAuth2 access token, which can be sent in a header or as an extra parameter. Such an approach is usually not vulnerable to CSRF attacks (I’ve seen exceptions), because the token must be manually added to each request. But whenever something is done manually it is often done wrong. For example, client code to add the token to a request may not check the URL of the request carefully, leading to tokens being sent to the wrong site, or attach the token to a non-HTTPS request therefore leaking the token to anybody in a position to sniff network traffic. Both of these problems are easily solved with standard and well-supported cookie attributes.
This kind of misuse of tokens, and the relative ease with which they can be exfiltrated from localStorage, has led OWASP to recommend tying the token to a local session cookie, configured with a restrictive set of security attributes: SameSite, Secure, HttpOnly, and with a __Host- prefix to tightly bind it to a single host. These measures ensure that if you do accidentally send the token over an insecure channel the cookie won’t go with it and so it won’t be any use. Again, we end up in a situation where you need both a cookie and a token in order to make an API call. This is effectively identical to the session cookie and CSRF token case, but in this case we say that the token carries the authority and the cookie is just for hardening.
Another example: I have recently been reworking some of the material on capability URLs in chapter 9 of my book. Capability URLs are nice because they are largely immune to CSRF-style attacks and encourage a least-privilege approach to security with many fine-grained tokens each granting minimal authority. They are also nice because they enable relatively safe and easy sharing in a natural way: if you want to share access to one particular photo you can just copy and paste the URL for that photo. But if the URL alone grants access and the same URL can be shared by many people then you lose accountability for who has done what. So I’ve been emphasising on a hybrid approach in which every user has a traditional session cookie too. Again, we end up with two tokens on every call: a session cookie for identity, and a token from the capability URL that grants access.1
I was recently reading a great blog post by Hanno Böck on making CSRF tokens safe against attacks like CRIME and BREACH that can extract tokens by exploiting a compression-based side-channel. The solution Hanno describes involves hashing the CSRF token with some indication of the operation being performed (a “scope”). The reason being is to ensure that if a token does leak that it can only be used for that one operation and not any others. In the limit this becomes very similar to the capability URL approach in which the token attached to a URL is only valid for a defined set of operations on the specific resource identified by that URL.
Yet another example of requiring two tokens crops up with the use of policy agents in ForgeRock’s Access Management product (AM). Agents manage access control on behalf of web applications and need to make API calls to AM to validate and inspect session cookies and perform policy evaluations. Users typically don’t have authority to make these calls themselves so the agents have their own session tokens and include both in the API requests: the agent token authorizes the request and the user token identifies the subject. Once again, two tokens: one for authority, one for identity.
I don’t believe this convergence of techniques around requiring two tokens to access an API or website is a coincidence. I also don’t think it is just working around historical issues with the web security model (although there is a bit of that). There is a fundamental principle here that identity should be separate from authority. The authority to perform a request should be explicit, transient, fine-grained, and least-privilege. Identity on the other hand is often ambient, persistent, and broadly scoped: my identity rarely changes and may even be shared across many sites (with or without my consent). By clearly separating the two we can enjoy the benefits of both. What we need now are frameworks to make such an approach more convenient, just like now widespread CSRF support.