In the last post I described some of the general problems with OAuth2 and its implementations. In this post I want to go into more detail and show some necessary hardening steps.
We did our best (well as much as a two people team can do) to implement as many security and validation checks as possible. If you find a bug, let us know.
Besides that standard web security practices, the following OAuth2 specific considerations apply:
You don’t need to implement every flow
The OAuth2 spec is huge. You probably only need a certain subset to get your job done. Don’t even try to implement every possible flow and protocol alternative.
We decided to implement authorization code, implicit and resource owner password flow only. That alone has reasonable complexity.
Token type and format
The OAuth2 spec does not specify a token format. We chose to use JSON Web Token (JWT) – and yes we implemented the token handling ourselves. This is not recommended, and since IdentityServer is based on .NET, we will change that to use the Microsoft JWT handler as soon as this leaves preview stage.
Redirect URI validation
This is a very common attack vector! If you don’t properly validate the redirect URI you might be sending the token or code to an unintended location.
We decided to a) always require SSL if it is an URL and b) do an exact match against the registered redirect URI in our database. No sub URLs, no query strings. Nothing. Unfortunately this breaks Microsoft’s OAuth support in ASP.NET 4.5 – but that’s a bug they have to fix. (see here).
Grant and response type validation
A number of recent hacks are based on the fact that grant and response types are input parameters. So e.g. someone could try to change the response type from code to token in the input query string, and – tada – suddenly some implementations return a token instead of just the authorization code.
We allow to bind the allowed flow to the client registration which in turn limits the allowed grant and response type. You should only allow one flow per client and rather create separate clients since they typically also have different trust levels (public vs confidential, trusted vs untrusted).
Bind tokens to the client
Make sure that when client 1 requests an authorization code that also only client 1 can use that code to request a token. Same for refresh tokens.
Don’t support credentials on the query string
We all know that credentials on query strings are the evil. Still they are allowed by the spec in certain situations (as an alternative to the authorization header). Say no to this madness and don’t implement it.
Protect client secrets
You wouldn’t store user passwords in clear text, would you? Same applies to client secrets. Use hashing – even better salted & iterated hashing. We use PBKDF2 (RFC 2898).
Encapsulated tokens are easy to get wrong
That’s why we use a handle based design to issue refresh tokens. That might not be as scalable, but makes me sleep better.
Further recommended reading:
- OAuth2 Threat Model
- Egor Homakov: OAuth1, OAuth2, OAuth…?
- Egor Homakov: OAuth2.a or Let’s just fix it