2

Please note: although I mention Spring Boot (and by proxy, Java) I believe this is a pure HTTP/web development question at heart, and as such, can be answered by anyone with CORS-configuration experience and zero Java experience!


I am configuring the CORS policy for a Spring Boot web service for the very first time. This service will live at, say, https://api.myapp.example.com and will only serve requests from an Angular app hosted at, say, https://myapp.example.com.

After doing some digging I found an example Spring Boot CORS configuration example, and even if you don't work with Java it should be pretty straight forward:

CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Arrays.asList("*"));
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
corsConfiguration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "X-Auth-Token"));
corsConfiguration.setExposedHeaders(Arrays.asList("X-Auth-Token"));

Basically:

  1. Allow requests from all (*) origins
  2. Allow requests using GET, POST, PUT, DELETE and OPTIONS methods
  3. Accept Authorization, Content-Type and X-Auth-Token headers
  4. Return an X-Auth-Token header in the response

My service will make use of a simple JWT-based scheme:

  1. User goes to the login page in the Angular app, enters username and password and clicks submit
  2. Angular app makes a POST to https://api.myapp.example.com/v1/login (JSON request body of {"username": "someone", "password": "12345"})
  3. Server authenticates them, and if thats a valid login, sends back a JWT in the response (but not as an X-Auth-Header)
  4. Angular app stores that JWT and sends it on each request as part of an authorization bearer header ("Authorization: Bearer <JWT>")

Having laid down all this background context:

  • I understand allowing all (*) origins is a security risk. What value should I set on my allowed origins that only accept requests from my Angular app?
  • Since I am using authorization bearer tokens and never using X-Auth-Header, I don't believe I need to allow/expose X-Auth-Token at all, correct?
hotmeatballsoup
  • 307
  • 2
  • 7
  • Please note that CORS **is not** a security feature. Anybody could just decide to circumvent CORS by not using a browser. It's like a paper sign at the back door that says "Please come up front". It's nice, it's helpful, but you still need to bolt that door, because a paper sign won't stop a burglar. – nvoigt May 18 '21 at 06:02
  • @nvoigt I respectfully disagree. I could do all my online banking via Curl and a command-line javascript engine, but I choose to do my online banking via an up-to-date browser instead. Why? Because browsers have built-in security features like Same-Origin Policy that help protect me. The fact that, yes, someone could attack the back with Curl does not change the fact that I am choosing to use a browser and I am getting real protection from CORS. – Mike Ounsworth May 18 '21 at 14:04
  • But CORS only asks the server whether it would accept your call. Any malicious attacker would obviously set up a server to accept your call no matter what. And anybody attacking a server would not use a browser. So what exactly does CORS protect you against? Which attack vector is closed due to CORS? – nvoigt May 18 '21 at 14:07
  • @nvoigt Same-Origin Policy is actually what's protecting against, say your `billsblog.com` tab attacking your banking tab -- either trying to make changes or read data. CSRF tokens also solve the same problem, but SOP works regardless of whether the server has CSRF tokens. CORS tells the browser when it's ok to weaken or ignore Same-Origin Policy, so technically CORS isn't protecting anything, but SOP is! – Mike Ounsworth May 18 '21 at 14:27

1 Answers1

4

As I wrote this, it kinda turned into paraphrasing the Mozilla CORS docs. The specific answers to your questions are tagged with "Suggestion:".

A little background on CORS

CORS policy is an extension of the Same-origin policy (specifically, CORS tells the browser what to do when a bit of javascript tries to make a cross-origin call). According to Mozilla, the definition of "same origin" is:

Two URLs have the same origin if the protocol, port (if specified), and host are the same for both.

Your two URLs are:

https://api.myapp.example.com
and
https://myapp.example.com

Same protocol (https), same port (unspecified), but different host, ergo the browser considers them different origins and will invoke CORS

Suggestion: So the first thing you could do is host your UI and API on the same protocol, port, and host, and then you don't need to worry about any of this mess. But I'll continue on assuming that you can't do that for whatever reason.


What value for Access-Control-Allow-Origin?

In this section I'm basically just quoting Mozilla's CORS page.

Here's how this is going to work: your UI's javascript is going to construct some request; probably by building up an XMLHttpRequest with body and headers and whatever, then it's gonna go to send it and the browser will go "whoah whoah, this is a non-simple cross-origin request, I need to do CORS before I send this request. It will send a "preflight" request with the OPTIONS HTTP verb to, basically, ask permission to send the real request. You may see your browser send a pre-flight like this:

OPTIONS /api/v1/phonenumber HTTP/1.1
Host: api.myapp.example.com
User-Agent: Mozilla/5.0
... snip ...
Origin: https://myapp.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Authorization, Content-Type, X-Auth-Token

Which, in plain English means:

Hi api.myapp.example.com, this is Firefox!

The javascript at https://myapp.example.com is trying to send you POST /api/v1/phonenumber and it set custom values on the following headers: {Authorization, Content-Type, X-Auth-Token} is it cool if I let that through?

If the server returns an error or anything other than a proper CORS response, then the browser will not send the actual POST. A successful CORS response might look like this:

HTTP/1.1 204 No Content
...
Access-Control-Allow-Origin: https://myapp.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type, X-Auth-Token
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin

Broken down, that basically means:

Hi Firefox!

Yes, you can send that to me. Also, in case it sends similar requests in the future, this URL can be called by https://myapp.example.com with GET, POST, PUT and DELETE, and with custom values for the Authorization, Content-Type, and X-Auth-Token headers. You don't need to ask me again for 86400 seconds. But please do ask me again if the same request comes with a different Accept-Encoding or Origin, because I may give a different answer.

Suggestion: If your JS will never set a value in the X-Auth-Token request header, then there's no need to specify it here.

Suggestion: Long story short, you want your API server to respond to OPTIONS requests with:

Access-Control-Allow-Origin: https://myapp.example.com
Vary: Origin

In your case, you can probably just hard-code that. For other readers, since you can't put a list here, the more general method is to look at the Origin: on the OPTIONS req, decide if that meets your rule or not, and if so then echo it back.


Access-Control-Expose-Headers

This tells the browser that javascript is allowed to read the specified headers. It sounds like you're returning your JWT in the response body, so you shouldn't need to set this, but if you were returning the JWT in, for example, an Authorization header that the JS needs to read, then you would need to specify that here.

Suggestion: If your server will never set a value in the X-Auth-Token response header, then there's no need to specify it here.


I think / hope my ramble has covered all your questions.

PS. CORS is stupidly complicated. As one of my colleagues recently said "The more I learn about CORS, the less I understand it". I am definitely still a student when it comes to CORS.

Mike Ounsworth
  • 57,707
  • 21
  • 150
  • 207
  • Absolutely **stunning** answer @Mike! Thank you so much! In 5 minutes you've brought me further than the last ~10 years in my understanding of CORS. I owe you a beer! – hotmeatballsoup May 18 '21 at 11:42
  • Also a quick followup if you don't mind: what should `Access-Control-Allow-Origin` be if the web service will be receiving requests from native devices (Android and iOS alike)? – hotmeatballsoup May 18 '21 at 12:41
  • 1
    Hurray! I'm glad to be helpful! (writing this out also helped me clarify a few things in my own understanding). For native devices, Curl, Postman, etc, I don't think it matters. None of those are browsers, none of those have the concept of "same origin" (I don't even know what it means to ask if an android app is "from the same origin" as the website API you're trying to reach...). So I imagine a app client will ignore any CORS headers anyway. – Mike Ounsworth May 18 '21 at 14:18