JSON API's Are Automatically Protected Against CSRF, And Google Almost Took It Away.
Disclaimer: Let me start by saying I am not advocating the built in protections of JSON API's against CSRF as a sole line of defense. As I will outline here, it's not a reliable protection
To understand how JSON API's are protected against CSRF we need to first understand a few things
- How Cross-Origin Resource Sharing (CORS) works
- What is CSRF
If you feel like you already have a solid grasp on these topics and the nuances between
non-simple HTTP requests, skip to the
So Why Are JSON API's Protected? Section
Cross-Origin Resource Sharing (CORS)
CORS outlines the policy which defines how a user visiting one origin can make requests to other origins and view the response to those requests. This policy is what defends against one origin from stealing another origin's data. It prevents Facebook from being able to steal your Gmail contents.
Simply put, while visiting one origin, you cannot make a request to a second origin and view the response without the consent of the second origin. This consent takes the form of the
Access-Control-Allow-Origin header in the response.
That said, there are cases where one origin can make requests to a second origin without consent, the first origin just won't be able to view the response. This nuance depends on whether the request is
When one origin attempts to make a
non-simple HTTP request to a second origin, the browser first makes what is called a
pre-flighted requests to check to see if the server gives consent to making the main request. See the image below to see in more detail how this works:
Simple requests skip this preflight entirely.
So what defines
non-simple requests? Well these rules can seem a little arbitrary, but I'll outline them below.
What is a Simple HTTP Requests?
SimpleHTTP requests are only the following methods:
SimpleHTTP requests can only have the following content-types:
SimpleHTTP requests cannot contain custom headers, and may only set the following headers
- Content-Type (but note the additional requirements above)
Anything else is a
So when do these rules become a problem?
Cross-Site Request Forgery (CSRF)
CSRF leverages the fact one origin can make credentialed
Simple HTTP requests to a second origin without consent. The first origin cannot view the response, but they can make the request. This becomes a problem is the server is configured to perform a state changing actions for authenticated users for
Simple HTTP Requests.
Credentials can take the form of any of the following
- Basic Authentication
- Integrated Windows Authentication
If a user has these credentials saved, one origin can force said user to make
simple requests with these credentials.
To solve this problem, most servers leverage the fact origin's can't view the response to requests, and they in affect, create their own preflight procedure to protect against
simple http requests. This usually takes the form of one request being made which fetches a token that's required for subsequent requests. More details on this can be seen here
So Why Are JSON API's Protected?
If you haven't figured it out already, JSON API's should strictly enforce the
application/json content-type. This makes the request
non-simple, and forces the browser to pre-flight all cross-origin requests.
Countless web applications take no steps to protect against CSRF, so this added safety net for JSON API's has been comforting to me. There have been many cases when doing bug bounties or pen-tests where it was clear no CSRF protection was added by the developer. For modern RESTFUL applications that only communicate over JSON these developers have a nice safety net they don't even know about.
Why Did Google Almost Take It Away?
What I'm referring to is this thread
Simply put, Google messed up when they implemented
navigator.sendBeacon and accidentally allowed
non-simple HTTP requests to be made cross-origin without preflight via the sendBeacon API.
This alone isn't a big deal, browsers have security problems all the time; they're usually fixed quickly.
What was concerning was this issue was open for 2 years, in part because of people in the thread who didn't understand the issue, and wanted to change the CORS rules instead of fix the issue. Here are a few comments from the thread:
For what it's worth, the requirement that we do a preflight for certain Content-Type values is a very silly one.
Since it's hard~impossible to predict the fallout from changing this behavior now, we're probably stuck with it. Well, at least for the features that have already shipped. Perhaps we could remove this requirement for new features moving forward?
To be clear, I'm not writing this to shame anyone, but simply to shed light on the issue, and illustrate how we almost lost what is in my view, an important built in browser protection against CSRF.