Knowing why we don’t use past methodologies can be just as useful as knowing why we use current ones. In this article, we are going to look at past methods for delegating access to an API (the problem that OAuth is the current solution for) and why we shouldn’t use them anymore. Examples in this article are based on systems I’ve seen in the wild or discussed on StackOverflow.
For a user to delegate access (or authorize, give permission) to a mail service to send emails on the user’s behalf. Only send permission must be allowed.
We’re going to use the OAuth terminology of:
- Client Application: The application we want to delegate access to
- Protected Resource: Your mail service (think the Gmail or a private mail server) exposed as an HTTP API. This resource is considered to be owned by the requesting user. Also referred to as our API.
Integrate with API
Credential sharing is where you simply give the client application your credentials for the protected resource, allowing it to authenticate with our mail service as if it was the user, and send an email.
And that’s the first issue, we are no longer delegating access, instead the client application is impersonating the user. This means that there is no scoped access, the application can do anything the user can. So instead of just being able to send emails, it can now delete them, create new contacts, change the user’s details, whatever it wants. This is a little more forgivable when all applications are within a single security domain (e.g. an intranet), but it’s still asking for trouble.
Revocation is also an issue, as this would involve the user changing their credentials for their entire account, instead of just this client application, which would also affect any other application using this technique and, let’s face it, the user is probably using this password for many other accounts.
We’re also unnecessarily exposing user credentials to the client application. Not only is this increasing the attack surface for our protected resource, but the client must store the user credentials in a reversible format so that they can later be relayed to our protected resource. As we know, passwords should be stored using a password hashing algorithm and never be stored in a reversible format (pain text or encrypted).
This also brings challenges to systems using multiple factors for authentication, as the authentication method used must have a constant value so that it can be stored and repeated at a later date. This means secure factors such as “something you have” using a local token generator such as Google Authenticator cannot be used. Federated identity is also not an option unless they support credential sharing (unlikely), so it’s local accounts or nothing.
Yet another limitation is with native or client side applications, which might not have a mechanism for storing credentials with any semblance of security, and therefore cannot use credential sharing without putting users at risk.
Integrate with API
A common solution is to have your protected resource generate a key for the user. This would typically be generated by the user within the protected resource. This could be a single API key or a mechanism that allows the user to generate keys. Out of the box this gives us removes a few of the downsides of credential sharing, we’re no longer exposing the user’s credentials, our API authorization method no longer affects what authentication methods we can use (both factors and external identity providers).
A key per client application is certainly more desirable, and using this mechanism you can start to address other concerns by building out the logic:
- Scoped Access (maybe configurable by the user on key generation)
- Key Revocation (assuming on key per app, we can revoke keys safely)
However, this approach still has its limitations. The credentials are still not explicitly linked to the client application, there’s no guarantee that the request using this API key came from the application it was generated for. Whilst this can also be true for bearer tokens used in OAuth, an API key has no expiration, meaning that until you discover it has been compromised and manually revoke it (assuming you built a revocation mechanism) the attacker has free reign over your system.
Assuming the client application is already aware of the protected resource, then we are also unnecessarily exposing the user to client credentials and forcing them to manage them. We already know how lazy we get with username and passwords; do you trust that API keys will be treated any differently? This has the potential for keys to be shared, making them harder to revoke, and relies on the user to know what permissions the client resource requires.
There’s a lot of custom code and architecture involved in getting this approach right, and even then, it has fundamental vulnerabilities. Based on how many failed implementations of OAuth there are, do you really think you’ll be able to both reinvent all of this logic and then implement it within this decade?
Another option that is very similar to the API Key method, is to create a user account specifically for the client application.
Yes, cookies protecting APIs is unfortunately still a thing. Whether it’s the protected resource sharing the same cookie as the client application, or prompting the user to log into the protected resource before in order to use it as seen above.
So, you log into your API and get a cookie. Great, now the client application can make requests. But if you open another tab in your browser and make a request manually to the API, you will still be successful. The problem here is that we are no longer authorizing the application to access our API, instead we are authorizing the browser. This opens up a world of pain in the form of Cross Site Request Forgery (CSRF or XSRF), where other applications will happily start using your API without your permission. Not good.
We also do not have much of a revocation path, as now we’d need to invalidate a cookie stored in a user’s browser by either waiting for them to make another request, or by changing our keys used to protect the cookie, invalidating every cookie for that protected resource.
I’m sure there are hacks around some of these issues, but the point still stands, cookies for APIs is a bad idea as they will always be open to CSRF.
Most of the above involves the protected resource being aware of user credentials and/or client credentials in some way. This isn’t unnecessary and doesn’t scale well, as the more APIs you create, the more manual one-to-one integrations you have to implement. The obvious solution would be to create a central authorization server and have your APIs trust this to make authorization and delegation decisions, which is exactly what OAuth does.
Let me know if you’ve seen anything else that you think should be added to this list (though try not to name and shame).