title: spring webflux CORS headers getting removed
body: <p><strong>EDIT</strong>: Please read the updates, the problem has been changed significantly.</p>
  
  <p>I am very badly blocked on this. I have a spring webflux app and I am trying to enable CORS headers on it, so that I am able to do requests from different origins in the same browser sessions. But whatever I do, the CORS headers are getting removed(even if I put them manually in the ServerResponse). Some of the classes in security/ and config/ I am using are:</p>
  
  <pre><code>package com.document.feed.security;
  
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.context.annotation.Bean;
  import org.springframework.http.HttpMethod;
  import org.springframework.http.HttpStatus;
  import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
  import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
  import org.springframework.security.config.web.server.ServerHttpSecurity;
  import org.springframework.security.web.server.SecurityWebFilterChain;
  
  import reactor.core.publisher.Mono;
  
      @EnableWebFluxSecurity
      @EnableReactiveMethodSecurity
      public class SecurityConfig {
  
          @Autowired
          private AuthenticationManager authenticationManager;
  
          @Autowired
          private SecurityContextRepository securityContextRepository;
  
          @Bean
          SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
              String[] patterns = new String[] {"/auth/**", "/vanillalist"};
              return http
                      .exceptionHandling()
                      .authenticationEntryPoint((swe, e) -&gt; Mono.fromRunnable(() -&gt; {
                          swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                      })).accessDeniedHandler((swe, e) -&gt; Mono.fromRunnable(() -&gt; {
                          swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
                      })).and()
                      .csrf().disable()
                      .authenticationManager(authenticationManager)
                      .securityContextRepository(securityContextRepository)
                      .authorizeExchange()
                          .pathMatchers(patterns).permitAll()
                          .pathMatchers(HttpMethod.OPTIONS).permitAll()
                      .anyExchange().authenticated()
                      .and()
                      .build();
          }
      }
  </code></pre>
  
  <p>SecurityContextRepository.java</p>
  
  <pre><code>package com.document.feed.security;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.http.HttpHeaders;
  import org.springframework.http.server.reactive.ServerHttpRequest;
  import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  import org.springframework.security.core.Authentication;
  import org.springframework.security.core.context.SecurityContext;
  import org.springframework.security.core.context.SecurityContextHolder;
  import org.springframework.security.core.context.SecurityContextImpl;
  import org.springframework.security.web.server.context.ServerSecurityContextRepository;
  import org.springframework.stereotype.Component;
  import org.springframework.web.server.ServerWebExchange;
  
  import com.document.feed.config.JwtTokenUtil;
  import reactor.core.publisher.Mono;
  
  @Component
  public class SecurityContextRepository implements ServerSecurityContextRepository {
  
      private static final Logger logger = LoggerFactory.getLogger(SecurityContextRepository.class);
  
      @Autowired
      private AuthenticationManager authenticationManager;
  
      @Autowired
      private JwtTokenUtil jwtTokenUtil;
  
      @Override
      public Mono save(ServerWebExchange serverWebExchange, SecurityContext sc) {
          throw new UnsupportedOperationException("Not supported yet.");
      }
  
      @Override
      public Mono load(ServerWebExchange serverWebExchange) {
          System.out.println("serverWebExchange:" + serverWebExchange.getAttributes());
          ServerHttpRequest request = serverWebExchange.getRequest();
          String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
          String authToken = null;
          if (authHeader != null &amp;&amp; authHeader.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
              authToken = authHeader.replace(JwtTokenUtil.TOKEN_PREFIX, "");
          }else {
              logger.warn("couldn't find bearer string, will ignore the header.");
          }
          System.out.println("SecurityContextRepository.authToken=" + authToken +
                      "\nauthHeader=" + authHeader);
          String username;
          try {
              username = jwtTokenUtil.getUsernameFromToken(authToken);
          } catch (Exception e) {
              username = null;
          }
          System.out.println("SecurityContextRepository.username:" + username);
          if (authToken != null) {
              Authentication auth = new UsernamePasswordAuthenticationToken(authToken, authToken);
              return authenticationManager.authenticate(auth).map((authentication) -&gt; {
                  SecurityContextHolder
                          .getContext().setAuthentication((Authentication) authentication);
                  return new SecurityContextImpl((Authentication) authentication);
              });
          } else {
              return Mono.empty();
          }
      }
  
  }
  
  package com.document.feed.config;
  
  import org.springframework.context.annotation.Configuration;
  import org.springframework.context.annotation.Import;
  import org.springframework.web.cors.CorsConfiguration;
  import org.springframework.web.reactive.config.CorsRegistry;
  import org.springframework.web.reactive.config.EnableWebFlux;
  import org.springframework.web.reactive.config.WebFluxConfigurer;
  
  import com.sun.org.apache.xerces.internal.parsers.SecurityConfiguration;
  
  @Configuration
  @EnableWebFlux
  @Import({CorsConfiguration.class, SecurityConfiguration.class})
  public class CorsGlobalConfiguration implements WebFluxConfigurer {
  
      @Override
      public void addCorsMappings(CorsRegistry corsRegistry) {
          corsRegistry.addMapping("/**")
                  .allowedOrigins("*")
                  .allowedHeaders("*")
                  .allowedMethods("GET", "POST", "PUT", "DELETE")
                  .allowCredentials(false)
                  .exposedHeaders("Access-Control-Allow-Origin",
                          "Access-Control-Allow-Methods",
                          "Access-Control-Allow-Headers",
                          "Access-Control-Max-Age",
                          "Access-Control-Request-Headers",
                          "Access-Control-Request-Method");
      }
  }
  </code></pre>
  
  <p>See, how headers <code>Access-Control-Allow-*</code> are dropped, while <code>Access-Control-Request-*</code> are retained in response headers.
  <a href="https://i.stack.imgur.com/P8OHQ.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/P8OHQ.png" alt="enter image description here"></a></p>
  
  <p>The error seen in chrome's console:</p>
  
  <pre><code>fetch('http://localhost:8080/vanillalist', {
    method: 'GET',
    headers: {
      'Content-type': 'application/json; charset=UTF-8'
    }
  })
  .then(res =&gt; res.json())
  .then(console.log)
  Promise {&lt;pending&gt;}
  2VM778:1 OPTIONS http://localhost:8080/vanillalist 404 (Not Found)
  (anonymous) @ VM778:1
  :3000/#/:1 Access to fetch at 'http://localhost:8080/vanillalist' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
  :3000/#/:1 Uncaught (in promise) TypeError: Failed to fetch
  </code></pre>
  
  <p><strong>Update:</strong> Image for the 3rd comment.<a href="https://i.stack.imgur.com/FsBk0.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/FsBk0.png" alt="3rd comment"></a></p>
  
  <p><strong>Update2:</strong> <code>curl -v</code> requested by @mikeb, after adding <code>OPTIONS</code> in request headers.</p>
  
  <pre><code>(venv) NB292:scaligent devansh.dalal$ curl -v http://localhost:8080/vanillalist &gt; /tmp/r.txt
    % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                   Dload  Upload   Total   Spent    Left  Speed
    0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying ::1...
  * TCP_NODELAY set
  * Connected to localhost (::1) port 8080 (#0)
  &gt; GET /vanillalist HTTP/1.1
  &gt; Host: localhost:8080
  &gt; User-Agent: curl/7.54.0
  &gt; Accept: */*
  &gt; 
  &lt; HTTP/1.1 200 OK
  &lt; transfer-encoding: chunked
  &lt; Vary: Origin
  &lt; Vary: Access-Control-Request-Method
  &lt; Vary: Access-Control-Request-Headers
  &lt; Content-Type: application/json
  &lt; Cache-Control: no-cache, no-store, max-age=0, must-revalidate
  &lt; Pragma: no-cache
  &lt; Expires: 0
  &lt; X-Content-Type-Options: nosniff
  &lt; X-Frame-Options: DENY
  &lt; X-XSS-Protection: 1 ; mode=block
  &lt; Referrer-Policy: no-referrer
  &lt; 
  { [8 bytes data]
  100 1350k    0 1350k    0     0  19.9M      0 --:--:-- --:--:-- --:--:-- 19.9M
  * Connection #0 to host localhost left intact
  </code></pre>
  
  <p><strong>Update3</strong>: The problem has been reduced now to handle the <code>OPTIONS</code> kind of requests.</p>