The Spring Framework appears to have an incorrect implementation of CORS. In particular, the framework rejects invalid CORS requests by sending an HTTP 403 response with message Invalid CORS request
.
https://github.com/spring-projects/spring-framework/blob/d84ca2ba90d27a7c63d7b35a6259b5b9cf341118/spring-web/src/main/java/org/springframework/web/cors/DefaultCorsProcessor.java#L97-L106
This is not how CORS is supposed to work. A CORS error is not meant to be generated on the server, rather it is meant to be generated by the browser itself. The role of the server in CORS is to either include or omit an Access-Control-Allow-Origin
HTTP header, and that's it. It is the browser's role to block the response if the response does not include an Access-Control-Allow-Origin
header matching the origin.
This is explained in section 3.5.4 of the book CORS in Action, quoted below:
So far we’ve covered what to do if you want to accept a CORS request. But what if you only want to allow CORS requests from certain origins and reject the others? CORS is strict in the sense that the Access-Control-Allow-Origin value must either be or an exact match of the Origin header. Regular expressions or multiple origins aren’t allowed; the Access-Control-Allow-Origin can only grant permissions to one origin at a time. If the Access-Control-Allow-Origin isn’t or an exact match of the Origin header, the browser rejects the request.
Table 3.5 summarizes the behavior for Origin and Access-Control-Allow-Origin header combinations. Rejecting a CORS request is as simple as:
- Sending an Access-Control-Allow-Origin header that doesn’t match the Origin header
- Removing the Access-Control-Allow-Origin header entirely
Table 3.5. How the browser reacts to server responses
Client request Server response Browser behavior Origin: http://localhost:1111 None Error. No Access-Control-Allow-Origin header. Origin: http://localhost:1111 Access-Control-Allow-Origin: * Success. Origin: http://localhost:1111 Access-Control-Allow-Origin: http://localhost:1111 Success. Origin: http://localhost:1111 Access-Control-Allow-Origin: http://othersite.com Error. Access-Control-Allow-Origin header doesn’t match Origin header. When you last modified the sample app, you updated the Access-Control-Allow-Origin header to only allow cross-origin requests from http://localhost:1111. Requests from any other origin will be rejected.
What does it mean for the browser to reject the request? It means that the browser doesn’t forward any of the response information to the client. The client only knows that an error occurred, but it doesn’t receive any additional information about what the error was. This can be frustrating when debugging CORS requests, because it’s hard to programmatically infer when a request fails due to CORS rather than some other reason. Chapter 7 delves more into how to debug failing CORS requests.
When the browser rejects the CORS request, it doesn’t send the response to the client. But the actual HTTP request is still made to the server, and the server still sends back an HTTP response. It may seem a little odd for the browser to make an HTTP request only to have it rejected. But this must be done because the browser has no way of knowing whether or not CORS is supported without first asking the server by making the request. Figure 3.22 shows the CORS flow when the server rejects the CORS request. The request is still sent to the server . When the browser notices that there is no Access-Control-Allow-Origin header (or the header doesn’t match the origin), it triggers an error on the client, and doesn’t forward the response details .
Note that there is no mention of rejecting the request with a 403 error response.
Comment From: bclozel
Any other kind of HTTP response is not successful and will either end up not being shared or fail the CORS-preflight request. Be aware that any work the server performs might nonetheless leak through side channels, such as timing. If server developers wish to denote this explicitly, the 403 status can be used, coupled with omitting the relevant headers.
I think this might be covered in chapter 6 of your book. We can discuss each error case in our implementation but I guess your point was in general for all error responses. Closing this issue for now.