July 18, 2013
First let’s look at this requirement of having an ‘open’ API, next we’ll look at some ways you can secure your API and finally we’ll dive into OAuth2 Implicit Grants.
The answer to that is that while you think you want an Open API, you should think long and hard before leaving an API call open for everyone to use. In fact all you are probably doing is opening yourself up for a Denial Of Service attack by abusive clients. Most of the time for an API you want to identify clients for rate limiting (you ARE implementing rate limiting in your API right?). Most popular APIs like Google Custom Search or Twitter are already doing this. As an aside if you’re not implementing rate limiting per client, go read Steve’s Platform Rant on how DOS attacks don’t just come from external services. Rate limiting can be as simple as an in memory store that increments a counter.
API Keys or HTTP Basic / Digest
Plain old Session Cookies
Enter OAuth2 Implicit Grant
Fortunately letting a user log in to another application is something that is a solved problem with OAuth. Unfortunately OAuth 1 wasn’t designed with SPAs in mind and relies on being able to identify the client only, which has the same problems as using a (secret) API key. Which is, partly, why OAuth 2 (RFC6749) introduced multiple ‘Grant types’:
- Authorization Code, ‘normal’ flow for ‘trusted clients’, like server to server communication
- Implicit, special flow for ‘clients that can’t keep secrets’.
- Resource Owner Password Credentials, backwards compatiblity, client can ask user for username / password
- Client Credentials, basically API keys, useful when user authentication is not necessary
Every flow is useful in it’s own context, but seeing as we’re interested in authentication with SPAs, let’s look deeper into OAuth2 Implicit Grant. Now in order to illustrate how the OAuth 2 Implicit Grant flow works, let’s use Twitter as an example.
The story of a login
This story is a ‘Happy Path’ scenario, it doesn’t account for token expiry (Access Tokens should expire after a short duration) or errors signing in. In order to get the full details I’d recommend reading the RFC.
Server implementation in PHP
OAuth1 had a record of needing complicated clients because of the custom signing, fortunately OAuth2 relies on TLS to provide a secure channel so clients can be relatively simple. While StackOverflow would recommend you handcode your own client by looking at this gist, Andreas Åkre Solberg (better known perhaps for his work on SimpleSAMLphp), has also written an excellent client called JSO.
OAuth2 is certainly not without controversy and if you’re going to rely on it significantly it’s helpful to understand it’s criticism. In July 2012, Eran Hammer resigned his role of lead author for the OAuth 2.0 project, withdrew from the IETF working group, and removed his name from the specification. Hammer pointed to a conflict between the web and enterprise cultures, citing the IETF as a community that is “all about enterprise use cases”, that is “not capable of simple.” What is now offered is a blueprint for an authorisation protocol, he says, and “that is the enterprise way”, providing a “whole new frontier to sell consulting services and integration solutions.” - OAuth2 Controversy on Wikipedia, clicking through on the links is certainly recommended. What follows are the main points against OAuth2 and how they relate to our use-case.
Not a protocol, it’s a framework to make protocols
Erans major point is that OAuth2 is too loose, you can’t make a generic client and/or server and expect it to cover all cases. However, as shown earlier, there is enough software that supports the most used parts of the specification and if you’re using something unsupported it shouldn’t be too hard to add. He raises a good point though: if you don’t know what you’re doing (at least on the server side) you’re likely to introduce vulnerabilities in the implementation. Which is why I recommend that if you can get away with NOT doing OAuth2, I would. However for certain use-cases there simply isn’t an option. If this is the case, you will need someone who has been trained in security. Though honestly, why would you let anyone NOT trained in security set up your authentication anyway?
Relies on proper SSL / TLS implementation in clients
Maybe this is a problem for native applications (which we’ll get to in a bit) where developers can ignore certificate errors? Browsers usually have pretty good TLS support, however it does make TLS the only line of real defence. This as opposed to signing, which offers some protection even without TLS. Which is important because TLS is hardly perfect. Certainly there is room for an additional layer of security here. However, even without this, it’s still good enough unless you’re dealing with highly sensitive data or functionality (I don’t care if Twitter uses it, but I would get nervous if my bank or a heart-lung machine only relies on TLS for security).
Don’t use OAuth2 on Native clients
One specific problem that Eran points out is that OAuth2 is unsafe for Native mobile apps. In order to guarantee that third party applications do not receive the user credentials the third party would have to use a webview (easily touchjacked) or redirect to a browser, neither of which are great user experiences. And in order to return to your application you would have to use a custom scheme (like Skype does) but unfortunately these are not protected, so a malicious app could steal it. This is valid criticism an a good reason to a different OAuth2 grant type such as** the OAuth2 Resource Owner Password Credentials grant type on Native applications and use username / password** until Google and Apple (with help from IANA?) make sure schemes are uniquely registered to an application.
Your access token may not actually come from the Authenticator
There is nothing stopping a user from copy-and-pasting an access token from another application / client into your application. So while the server is still correct in the user that’s authenticated, there is a mismatch between who the server thinks it’s talking to and who it’s actually talking to. Example: I grant access to Facebook to use my Twitter, then I am redirected back to Facebook with an Access Token in the URL. I can then take this access token, and hand it to OAT. Now Twitter will think Facebook is requesting the data, when in fact OAT is. In practice this probably won’t be an issue unless you’re using client specific code (a code smell in its self) or relying on the ‘scope’ feature too much (hint: don’t!). The scope feature allows you to give a specific client access to only a specific part of your API. For instance if you have a website that always has a scope ‘all’ and an admin environment where you can have a scope ‘editor’ or ‘all’. The scope ‘editor’ would allow the user to only place tweets, the scope ‘all’ might also allow the user to block tweets. Simply copying the Access Token from the website to the admin environment would grant the user extra rights! A simple solution would be to, in the client, check the Client ID of the access token received with the Authentication Server. However I would generally advise to not abuse ‘scope’ for authorization or write client specific code, in which case this is a non-issue.
Long specification time
Unfortunately OAuth2 took 3 years to specify. In the mean time big players like Google and Facebook needed to have a solution, so they implemented draft specifications. And where these big players go, others follow. So we end up with a fair number of services and clients that support different versions of the OAuth2 draft specifications which may or may not be compatible with the final version. With time these clients and services will be updated or ignored. It’s inconvenient for now, but will only get better in the future.
OAuth2 solves our use-case neatly, but it’s not an easy solution and does require some security knowledge to properly implement. It’s also still a relative young specification. That said, it already has some serious services (Facebook, GitHub, Google, SalesForce, etc) running it. If your use-case matches what OAuth2 has to offer, and you can’t use another simpler protocol, you might want to have a look at OAuth2 (Implicit Grants). At least until Eran (or someone else) comes up with something better. Thanks to Geert van der Ploeg for reviewing this for factual errors.