In the previous post, we discussed CORS, CSRF tokens, SameSite, clickjacking and JSON hijacking. With all that preparation, we can finally answer the question if CSRF tokens can be used in a SPA, or not?.
If you paid good attention to the previous article, you might already know the answer. Yes! But there are some things we have to be careful of. Also, how would you set it up?
Setup
This article assumes you already have a backend that provides a CSRF token solution.
There is actually more than one way to set this up, depending on what your backend's CSRF token solution offers, I will introduce two approaches.
Approach 1: A cookie
- The client initializes CSRF protection by calling an endpoint on the API server that sets a cookie with httpOnly set to false holding the CSRF token
- For subsequent API requests, the client grabs the cookie from
document.cookie
, and passes it to the request (usually in the header under "X-CSRF-TOKEN") - With each API request, the API server updates the cookie with a fresh token.
For the API server to place a cookie that the client can read, the SPA and the API server have to be on the same domain.
Approach 2: An API endpoint
- The backend needs to provide an API endpoint (e.g.
/csrf
) that returns a CSRF token - The client calls this endpoint to get their token
- The CSRF token is then passed into each subsequent API request similar to approach 1
This approach works also if SPA and API are on a different domain.
🤔 When should the client get a new CSRF token?
There are a few options:
- Grab a new token each time before making a request by calling "/csrf" again
- Make the backend return a new token as part of every API response, and make the client use it for the next request
🤔 How is the CSRF token endpoint secured?
- The API server must have CORS set to allow only trusted sites like the SPA. This is to prevent third-party AJAX requests from getting hold of a CSRF token that works for you.
- The endpoint's response must not be JSON hijackable.
{ token: 'xxx' }
will do.
🤔 Can an attacker not just call the "/csrf" endpoint from a server, where CORS doesn't apply?
This question was actually covered in my previous article, not for API endpoints, but for grabbing a CSRF token from a form in the HTML.
But it's the same story: CSRF tokens are typically bound to the user's session, so your CSRF tokens won't work for me.
Finally, depending on the solution you pick, and your backend setup, you might have to add logic to handle token expiry. For example, by redirecting to the login page when receiving a 401 or 419 status.
Next time, let's see how auth cookies should be secured.
Top comments (3)
Great details! I was wondering, should we be guarding against JSON hijacking in 2021? Based on stackoverflow.com/a/16880162/771768 it seems to have been fixed in Chrome a decade ago.
Yea, it was fixed ages ago, but with ES6 proxies, it resurfaced. Now everything is fine again, but it could appear again with new browser features.
But there are also other security mechanisms since then to protect from it like SameSite, CORB (JSON hijacking is referred to as XSSI in that article), and the nosniff header.
Nice one!