Bug 63937

Summary: CORS preflight request not possible on authenticated endpoints
Product: Tomcat 9 Reporter: Michael Osipov <michaelo>
Component: CatalinaAssignee: Tomcat Developers Mailing List <dev>
Status: RESOLVED FIXED    
Severity: normal CC: michaelo
Priority: P2    
Version: 9.0.x   
Target Milestone: -----   
Hardware: All   
OS: All   

Description Michael Osipov 2019-11-19 10:15:11 UTC
Consider the following web.xml snippet:

> <filter>
> 	<filter-name>apiCorsFilter</filter-name>
> 	<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
> 	<init-param>
> 		<param-name>cors.allowed.origins</param-name>
> 		<param-value>https://...</param-value>
> 	</init-param>
> 	<init-param>
> 		<param-name>cors.exposed.headers</param-name>
> 		<param-value>Correlation-Id,Content-Length,Content-Disposition,Location</param-value>
> 	</init-param>
> 	<init-param>
> 		<param-name>cors.support.credentials</param-name>
> 		<param-value>true</param-value>
> 	</init-param>
> </filter>
> <filter-mapping>
> 	<filter-name>apiCorsFilter</filter-name>
> 	<url-pattern>/api/*</url-pattern>
> </filter-mapping>
> 
> <security-constraint>
> 	<web-resource-collection>
> 		<web-resource-name>Authenticated REST Services</web-resource-name>
> 		<url-pattern>/api/*</url-pattern>
> 	</web-resource-collection>
> 	<auth-constraint>
> 		<role-name>...</role-name>
> 	</auth-constraint>
> </security-constraint>

A CORS preflight will fail with 401 because a configured authenticator will kick in before the CORS filter.

According to https://fetch.spec.whatwg.org/#http-responses and https://fetch.spec.whatwg.org/#ref-for-credentials%E2%91%A5 regardless of the browser config with fetch/XHR API and credentials in "include" mode, all preflight requests are anonymous by default.

You may Google for "cors preflight bypass authentication". The solutions on the web, by omitting OPTIONS with <http-method-omission /> as in https://developer.ibm.com/answers/questions/405007/is-it-possible-to-exclude-http-options-method-from/, are incomplete and pose a security risk.
Consider that a client issues a regular OPTIONS request not related to CORS and no credentials are passed you will not able to properly serve the "Allow" header if you enforce some kind of ACLs on your resources.

My proposal is to add a boolean property to AuthenticatorBase which will detect a CORS preflight request and bypass authentication, but all other OPTIONS requests will require authentication as before.
Comment 1 Mark Thomas 2019-11-26 20:51:05 UTC
allowCorsPreFlight looks like a reasonable attribute name.

I'd suggest, if it is possible without too much jumping through hoops, it defaults to true in the presence of the CORSFilter and false otherwise.
Comment 2 Michael Osipov 2019-11-26 21:06:28 UTC
(In reply to Mark Thomas from comment #1)
> allowCorsPreFlight looks like a reasonable attribute name.
> 
> I'd suggest, if it is possible without too much jumping through hoops, it
> defaults to true in the presence of the CORSFilter and false otherwise.

I agree, having allowCorsPreflight (note lowercase F as in the Fetch Standard) to be on by default n and can be disabled for whatsoever reason.
Comment 3 Michael Osipov 2019-11-26 21:07:30 UTC
(In reply to Mark Thomas from comment #1)
> allowCorsPreFlight looks like a reasonable attribute name.
> 
> I'd suggest, if it is possible without too much jumping through hoops, it
> defaults to true in the presence of the CORSFilter and false otherwise.

How do you want to detect that actually from a valve? What is not our CORS filter is active, but another one, e.g. from Spring MVC, but uses CMS?
Comment 4 Mark Thomas 2019-11-27 14:52:58 UTC
Corrected attribute name noted.

From a Valve we should be able to look into the FilterChain so see if our CORS valve is present. For other implementations the user would have to set it manually. If there are a small number of standard CORS filter implementations with *&might* be able to look for each of them (by fully qualified class name) but this starts heading in a direction I'm rather uncomfortable with.
Comment 5 Michael Osipov 2019-11-27 14:55:49 UTC
(In reply to Mark Thomas from comment #4)
> Corrected attribute name noted.
> 
> From a Valve we should be able to look into the FilterChain so see if our
> CORS valve is present. For other implementations the user would have to set
> it manually. If there are a small number of standard CORS filter
> implementations with *&might* be able to look for each of them (by fully
> qualified class name) but this starts heading in a direction I'm rather
> uncomfortable with.

Sounds like a good plan. I fully agree with. I would neither peek for third-party filters, an explicit true on that attribute is fine.
Comment 6 Mark Thomas 2019-11-30 19:02:40 UTC
Drat. The filter chain is populated later in the request processing chain than I thought it was. I'm looking into alternatives for the "If the CORS filter is present" option.
Comment 7 Mark Thomas 2019-12-02 18:03:51 UTC
Fixed in:
- master for 9.0.30 onwards
- 8.5.x for 8.5.50 onwards
- 7.0.x for 7.0.99 onwards