NOTE: The reason OWASP recommends CSRF tokensonly using the Origin header as a secondary measure to CSRF tokens is that the Origin header didn't yet support all common browsers when the recommendation was made. All common browsers have supported this feature for quite some time now. (It is currently ~3-4 years old)
NOTE: The reason OWASP recommends CSRF tokens as a secondary measure is that the Origin header didn't yet support all common browsers when the recommendation was made. All common browsers have supported this feature for quite some time now. (It is currently ~3-4 years old)
NOTE: The reason OWASP recommends only using the Origin header as a secondary measure to CSRF tokens is that the Origin header didn't yet support all common browsers when the recommendation was made. All common browsers have supported this feature for quite some time now. (It is currently ~3-4 years old)
Our conversation started in the comment section, but I realized some inaccuracies in what I wrote. The sharing of cookies across domains is stricter than I thought at first. This should be a more comprehensive overview, and closer to what you might be looking for.
I will make a few assumptions:
- You have two domains:
foo.com(ui) andbar.com(api) - You want to prevent another domain like
evil.comfrom causing side effects/reading responses frombar.com(CSRF + CORS protection)
Approach 1: Using CSRF tokens
This can be done using cookies, or simply using custom headers and storing the values in session storage or as a hidden input in a form. This means you manually need to send the CSRF tokens as custom headers with every request. Both from server and client.
Cookies will not be a good option here, since foo.com and bar.com are separate domains.
Limitations of cookies for different domains (that are not subdomains of the same domain)
foo.com --(request)--> bar.com
- Any cookies stored in your browser with
Domain=bar.com(that does not includeSameSite=laxorSameSite=strict) will be sent along with the request assumingwithCredentials=true
foo.com <--(response)-- bar.com
Set-Cookieis an illegal header in cross-domain requests, as mentioned (https://fetch.spec.whatwg.org/#forbidden-response-header-name).- Trying to set a cookie with
Domain=bar.comusing JavaScript while atfoo.comwill not work either.
bar.com --(request)--> bar.com
- Here,
Set-Cookiewill work. Hence a redirect fromfoo.com->bar.comand then back will be able to set a cookie withDomain=bar.com. (So this could be a somewhat ugly/slow workaround)
Approach 2: Checking the Origin header
This is probably the simplest/cleanest option. (You won't need CSRF tokens if you use this method).
NOTE: The reason OWASP recommends CSRF tokens as a secondary measure is that the Origin header didn't yet support all common browsers when the recommendation was made. All common browsers have supported this feature for quite some time now. (It is currently ~3-4 years old)
*.com --(request)--> bar.com (CSRF)
Make sure that you check the Origin header of the incoming request in the bar.com-server. If this is either missing or https://foo.com, the request should be accepted. Otherwise, the response should be something like 403 (Unauthorized).
*.com <--(response)-- bar.com (CORS)
The response needs to have the proper CORS headers (assuming it was accepted, and the Origin header is not missing):
// Something like this, depending on your server language response.setHeader('Access-Control-Allow-Origin', request.getHeader('Origin'))