What the OAuth spec says
First, we need to accept that because that this is a public client under control of the user, we’ll have to accept that it is impossible to completely prevent client impersonation. You always could impersonate a client with cURL or a web scraper, which is something that is out of the control of the API owner. To prevent this, we’d need some kind of trusted computing architecture where we are 100% certain that the client credentials are protected from prying eyes.
Both types of impersonation are already well-known and have solutions in other Internet standards that we can use for this case.
Preventing compromised client source code
For this one, we can simply use SSL for the client’s domain. If the source code has been compromised through a man-in-the-middle attack, the user will see an SSL error in the browser. The OAuth spec already requires that communication to the authorization server’s token and authorization endpoints occur over SSL. It is permitted in the OAuth spec to have a client delivered over HTTP, however.
If your client is on the same origin as your authentication server
If you are running a client on the same origin as the authentication server, requests to the authentication server will be permitted through “normal” AJAX and I believe that all you will need to do is not permit cross-domain requests (i.e. don’t enable CORS) on your authentication server and the ROPC flow will be unavailable to impersonating clients. Here’s why:
- It is not possible for a third-party (an intermediate proxy) to intercept the token in this way because the browser will be communicating with your server over SSL (you are using SSL for your authentication server, right!?).
- You need to ensure that potentially-impersonated POSTs to your token endpoint are not in any way destructive. Typically, CSRF attacks (of which this technically is one) lead to a compromise by either setting a cookie that is later used to access a protected resource or cause a POST that takes an abusive action (withdrawing money). You’ll need to ensure that a POST to your token endpoint doesn’t do either of these things.
If your client is on a different origin from your authentication server
If you are running your client on “yourdomain.com” and your API server on “api.yourdomain.com”, you will need to implement CORS anyway. In this case, you should leverage CORS to validate the client. Here’s how you can do it:
Since IE9 and below don’t implement CORS correctly, many sites implement work-arounds such as iframe proxies or Flash-based work-arounds. I haven’t looked into the implications of using these, but they definitely need careful consideration to make sure they are not exploitable.
You absolutely should implement some kind of rate-limiting on your token endpoint to prevent brute-force attacks.
Finally, you should never issue public clients a refresh token (or any long-lived access token). The reason for this is that, depending on your backend architecture, these could be difficult to revoke should you need to revoke access to a specific client. For example, if you are using a JSON Web Token instead of a database record, you would need to blacklist all of them it to revoke them.
OAuth2 is still relatively new (as is CORS), so if I’ve missed any ways for this to be exploited, let me know in the comments! Thanks.