Confused about how to handle CORS OPTIONS preflight requests

Eric B. picture Eric B. · Feb 15, 2014 · Viewed 41.4k times · Source

I'm new to working with Cross Origin Resource Sharing and trying to get my webapp to respond to CORS requests. My webapp is a Spring 3.2 app running on Tomcat 7.0.42.

In my webapp's web.xml, I have enabled the Tomcat CORS filter:

<!-- Enable CORS (cross origin resource sharing) -->
<!-- http://tomcat.apache.org/tomcat-7.0-doc/config/filter.html#CORS_Filter -->
<filter>
  <filter-name>CorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>CorsFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>   

My client (written with AngularJS 1.2.12) is trying to access a REST endpoint with Basic Authentication enabled. When it makes it's GET request, Chrome is first preflighting the request, but is receiving a 403 Forbidden response from the server:

Request URL:http://dev.mydomain.com/joeV2/users/listUsers
Request Method:OPTIONS
Status Code:403 Forbidden
Request Headers:
   OPTIONS /joeV2/users/listUsers HTTP/1.1
   Host: dev.mydomain.com
   Connection: keep-alive
   Cache-Control: max-age=0
   Access-Control-Request-Method: GET
   Origin: http://localhost:8000
   User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36
   Access-Control-Request-Headers: accept, authorization
   Accept: */*
   Referer: http://localhost:8000/
   Accept-Encoding: gzip,deflate,sdch
   Accept-Language: en-US,en;q=0.8
Response Headers:
   HTTP/1.1 403 Forbidden
   Date: Sat, 15 Feb 2014 02:16:05 GMT
   Content-Type: text/plain; charset=UTF-8
   Content-Length: 0
   Connection: close

I'm not entirely sure how to proceed. The Tomcat filter, by default, accepts the OPTIONS header to access the resource.

The problem, I believe, is that my resource (the request URL) http://dev.mydomain.com/joeV2/users/listUsers is configured to only accept GET methods:

@RequestMapping( method=RequestMethod.GET, value="listUsers", produces=MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public List<User> list(){
    return userService.findAllUsers();
}

Does this mean that I must make that method/endpoint accept OPTIONS method as well? If so, does that mean I have to explicitly make every REST endpoint accept the OPTIONS method? Apart from cluttering code, I'm confused how that would even work. From what I understand the OPTIONS preflight is for the browser to validate that the browser should have access to the specified resource. Which I understand to mean that my controller method should not even be called during the preflight. So specifying OPTIONS as an accepted method would be counter-productive.

Should Tomcat be responding to the OPTIONS request directly without even accessing my code? If so, is there something missing in my configuration?

Answer

Eric B. picture Eric B. · Feb 15, 2014

I sat down and debugged through the org.apache.catalina.filters.CorsFilter to figure out why the request was being forbidden. Hopefully this can help someone out in the future.

According to the W3 CORS Spec Section 6.2 Preflight Requests, the preflight must reject the request if any header submitted does not match the allowed headers.

The default configuration for the CorsFilter cors.allowed.headers (as is yours) does not include the Authorization header that is submitted with the request.

I updated the cors.allowed.headers filter setting to accept the authorization header and the preflight request is now successful.

<filter>
  <filter-name>CorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
    <init-param>
        <param-name>cors.allowed.headers</param-name>
        <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization</param-value>
    </init-param>     
</filter>

Of course, I'm not sure why the authorization header is not by default allowed by the CORS filter.