OAuth 2.0 with Single Page Applications

Single Page Applications (SPA) are known as untrusted clients. All the API calls from an SPA are made from a java-script (or any scripting language) running in the browser.

The challenge is how to access an OAuth secured API from an SPA?

Here, the SPA is acting as the OAuth client (according to the OAuth terminology), and it would be hard or rather impossible to authenticate the OAuth client. If we are to authenticate the OAuth client, the credentials should come from the SPA - or the java-script itself, running in the browser - which basically open to anyone who can see the web page.

The first fundamental in an SPA accessing an OAuth secured API is - the client cannot be authenticated in a completely legitimate manner.

How do we work-around this fundamental?

The most common way to authenticate a client in OAuth is via client_id and client_secret. If we are to authenticate an SPA, then we need to embed the client_id and client_secret to the java-script. This will give anyone out there the liberty to extract out those from the java-script and create their own client applications.

What can someone do with a stolen client_id/client_secret pair?

They can use it to impersonate a legitimate client application and fool the user to get his consent to access user resources on behalf of the legitimate user.

OAuth has security measures to prevent such illegitimate actions, by itself.

Both in the authorization code and the implicit grant types, in the grant request the client can send the optional parameter redirect_url. This tells the authorization server, where to redirect the user with the code (or the access token) - after authenticating and providing the consent at the authorization server. As a counter measure for the above attack, the authorization server must not just respect the redirect_url in the grant request blindly. It must validate it against the redirect_url registered with the authorization server at the time of client registration. This can be an exact one to one match or a regular expression.

In this way, even if the client_id and client_secret are stolen, the rogue client application will not be able to get hold of the access_token or any of the user information.

Then what is the risk of loosing the client_id and client_secret?

When you register an OAuth client application with an authorization server - the authorization server enforces throttling limits on the client application. Say for example, a given client application can only do 100 authentication requests within any one minute time interval. By stealing the client_id and client_secret the rogue client application can impact the legitimate application by eating all available request quota - or the throttling limit.

How do we avoid this happening in a SPA, where both the client_id and client_secret are publicly visible to anyone accessing the web page?

For an SPA there is no advantage in using the authorization code grant type - so it should use implicit grant type instead. authorization code grant should only be used in cases where you can protect the client_secret. Since an SPA cannot do that - you need not to use it.

One approach to overcome this drawback in an SPA is - make the client_id - a one-time-thing. Whenever you render the java-script - you get a new client_id and embed the new client_id in the java-script, and invalidate it - at its first use. Each instance of the application, rendered on the browser will have its own client_id, instead of sharing the same client_id in all the instances.

At the authorization server end, all these generated client_ids will be mapped to a single parent client_id - and the throttling limits are enforced on the parent client id.

Now if a rogue client application still want to eat the full or part of the request quota assigned to the legitimate application - then for each request it has to load the legitimate web application and scrape through it to find the client_id embedded in it and then use it. That means for each authentication request that goes to the authorization server - should have a request that goes to the SPA to load the java-script, prior to that. This can be protected by enforcing denial of service attack protection measures at the SPA end - and possibly black list the rogue client.

The next challenge is how to protect the access token. In an SPA access token will also be visible to the end user. When using implicit grant type the access token will be returned to the browser as an URI fragment and will be visible to the user. Now, the user (who is a legitimate one) can use this access token (instead of the application using it) to access the back-end APIs - and eat all the API request quota assigned to the application.

The second fundamental in an SPA accessing an OAuth secured API is - the access token cannot be made invisible to the end-user.

One lighter solution to this is - enforce a throttling limit at the per client per end-user level - in addition to the per client level. Then - the rogue end-user will just eat up the quota of his own - won't affect the other users accessing the same application.

Let's take another scenario - say its not the end user - but someone else steals the user's access token from the URI fragment and then wants to use it to access resources on behalf of the legitimate user.

As a protective method for this, we need to make the lifetime of the access token returns back in the URI fragment extremely short and also in its first usage, invalidate it. To get a new access token - immediate one (the access token) before has to be provided - in case authorization server finds the sequence (of access token) is broken at any point then it will invalidate all the access tokens issued against the original access token returned back in the URI fragment. This pattern is known as 'rolling access tokens'.

In summary, this blog suggests three approaches to protect single page applications in accessing OAuth 2.0 secured APIs.
  • One-time client_ids mapped to a single parent client_id
  • Per application per user throttling policies
  • Rolling access tokens