When are OPTIONS Requests Sent?
In this post we’ll look at OPTIONS requests, when they get sent, and their security implications.
A basic understanding of CORS and CSRF attacks would be beneficial before reading this post.
Same Origin Requests
We’ll start off by looking at requests from the same origin.
Below we have loaded our testing website my-testing.com
, which is a local web server I’m hosting.
If we call one of the server APIs, we will have no issues as the request is not cross-origin. The response body {result: true}
is also visible in the browser:
The result is the same if add additional headers and a body to the request:
Cross Origin Requests
Let’s see the difference when we start performing requests to my-testing.com
from another origin.
HTML Form Submission
On a different site we’ll create and submit a HTML form using JavaScript:
Using an interception proxy (i.e. burp), we can see the request details:
- Notice the
Origin
header indicating where the request came from. - Also notice the session token (cookie) was included.
From the response we can see that the request was successful:
- If this was a state changing request like
create-user
,update-details
, etc, then congrats because we just performed a CSRF (cross-site request forgery) attack.
However, no OPTIONS requests have been sent so far, so why not?
Additional Headers
Let’s now assume that the /endpoint
API on my-testing.com
accepts a JSON body instead of a HTML form. We can use the fetch
command in JavaScript to update the content-type
header, add a JSON body, and perform the request:
- We’re unable to see the response in the browser due to missing CORS headers, but this might not matter if it’s a state changing request, as the action may have still been performed by the server.
Let’s see what request(s) were sent using our proxy:
Now an OPTIONS request was sent, but no POST request.
Because we added extra headers, the browser sends a preflight request (OPTIONS) to ensure this origin (node-security.com
) has permission to send requests to the target origin (my-testing.com
).
If we want the OPTIONS request to succeed, then we need to add the following CORS headers:
Access-Control-Allow-Origin
Access-Control-Allow-Headers
Access-Control-Allow-Methods
Access-Control-Allow-Credentials
Let’s call the API again now with the correct headers added to the OPTIONS response:
The request still “failed” because of CORS issues, but again, this may not matter if the POST request was sent.
Looking at our proxy history we see the following:
We can see that the correct headers were added to the OPTIONS response which means the browser will perform the POST request. The POST request still “failed” because there are still missing CORS headers on the response.
If we wanted no errors, the following response headers need to be added:
Access-Control-Allow-Origin: https://node-security.com
Access-Control-Allow-Credentials: true
Summary
OPTIONS requests are sent when additional headers have been added to cross-origin requests. The request is a preflight check to see if the origin is allowed to perform the request.
Security Implications
The question we want to ask is do OPTIONS requests provide protection against CSRF attacks? This is similar to asking if CORS provides protection against CSRF attacks?
The answer to both of these, annoyingly, is yes and no. There are situations when it does, but also situations when it doesn’t.
A better answer is that it’s not a reliable protection mechanism against CSRF. Better mechanisms include Anti-CSRF tokens (sometimes), utilizing the SameSite flag on cookies, and checking the Origin header server-side.