Confused how to properly authenticate access an API when using a browser-based application? Then use the below cheat sheet to choose the right approach for your needs.
I have an application running within the context of the browser (e.g. a React or Angular Single Page Application (SPA)) that wants to access an API on behalf of a user. This authenticated API call will be made directly from the user’s browser, and only our application should be able to call it on behalf of our authenticated user (i.e. we’re not vulnerable to Cross-Site Request Forgery (CSRF/XSRF).
The Cheat Sheet
Let’s break down our options using some criteria. Not all the criteria are earth-shattering vulnerabilities that demand an immediate application rewrite; however, they can be used to decide which approach to take based on the risk profile of our application.
|Method||Access credentials can be securely stored||Access credentials secure during auth||Can be used across domains||Secure against CSRF||Speed|
|“Just use a damn cookie”||fast|
|OAuth Implicit Flow||fast|
|OAuth Auth Code + PKCE||auth: average
|OAuth + Backend for Front End||auth: average
All options should include CSP as standard. XSS is a risk that undermines all of the above approaches.
OAuth Implicit Flow
- Speed: fast (1 round trip for auth, access token included in subsequent requests)
- Largest Concern(s): access token visible during auth process & access token cannot be stored in browser securely
The OAuth implicit flow has been our go-to approach up until recently due to a renewed discussion in the OAuth working group. This approach involved getting access tokens directly from an OAuth authorization server, where tokens where being returned directly from the authorization endpoint (the thinking was that the client couldn’t securely authenticate itself, so why bother with the authorization code and token endpoint roundtrip).
The use of OAuth meant we solved CSRF and XSS concerns when using cookies to access an API.
However, using this approach leaves access tokens vulnerable within the browser, and also the URL when they are transported within the browser. Nor was there a way from the client application to very that the tokens it received were the tokens intended for it.
Mitigations when using this flow include using OpenID Connect identity tokens (including nonce and at_hash validation), and the URL hash fragment as the response mode.
OAuth Authorization Code + PKCE
- Speed: average-fast (2 round trips for auth, access token included in subsequent requests)
- Largest Concern(s): access token cannot be stored in browser securely
By using Proof Key for Code Exchange (PKCE), we can provide OAuth a mechanism to remove access tokens from the URL entirely (whether using the query string or hash fragment), while also giving the authorization server a mechanism of verifying that the authorization code has not been stolen.
However, we’re still left with the issue of where to put that token once we receive it.
Also, some of the benefits that PKCE gives us may be undermined by how we store the
code_verifier (again, it’s stored in the browser).
Further mitigations include using OpenID Connect identity tokens (including nonce and maybe c_hash validation (see hybrid flow’s code id_token response mode)), and, again, the URL hash fragment as the response mode.
Same Domain Applications
- Speed: fast (1 round trip for auth, cookies then attached to subsequent requests)
- Largest Concern(s): limited to an application running on the same domain as the API, plus we lose the other benefits of using OAuth
One of the initial driving factors behind OAuth was the mitigation of CSRF when using APIs. With the increased support of same-site cookies, the CSRF concern with cookies and APIs has lessened, with same-site cookies telling the browser to only attach the cookie to the request when the request comes from the same domain that issued the cookie.
This approach limits our client application to be hosted on a single domain; but, if that’s all you need, then this is a straightforward solution to authenticated API access.
Backend for Frontend
- Speed: average-slow (2 round trips for auth, double the number of requests)
- Largest Concern(s): Not an overnight change for existing applications
A different approach, recently detailed by Dominick Baier, is to remove API access from the browser completely, instead, using a lightweight server to handle OAuth requests, sessions, and token storage.
This server backend triggers handles authentication and any communication with an OAuth authorization server, uses the same-site cookies to authenticate calls between the frontend application running in the browser, and the backend server, and then uses OAuth to talk to the API.
Using this combination of same-domain applications and OAuth, we prevent access tokens from ever appearing in the context of the browser, while still preventing CSRF. However, we’re gaining full security at the expense of speed due to each API request now being repeated by the backend server.
Check out Dom’s article “An alternative way to secure SPAs (with ASP.NET Core, OpenID Connect, OAuth 2.0 and ProxyKit)” for more details and a reference implementation.