403 response to a CORS preflight request from an angular app to a spring security REST API caused by a Access-Control-Request-Headers mismatch

CORS is a pain, I’ve been struggling with a POST to an API whose CORS preflight OPTIONS request was rejected with a 403.

I was going crazy because while the OPTIONS request was rejected when executed from the navigator, it succeeded when executed from the command line using CURL.

curl -v -H "Access-Control-Request-Method: POST" -H "Origin: http://localhost:4200" -X OPTIONS http://localhost:8080/api/v1/documents/upload

 Trying ::1…
 TCP_NODELAY set
 Connected to localhost (::1) port 8080 (#0) 
   OPTIONS /api/v1/documents/upload HTTP/1.1
   Host: localhost:8080
   User-Agent: curl/7.55.1
   Accept: /
   Access-Control-Request-Method: POST
   Origin: http://localhost:4200
   < HTTP/1.1 200
   < Vary: Origin
   < Vary: Access-Control-Request-Method
   < Vary: Access-Control-Request-Headers
   < Access-Control-Allow-Origin: *
   < Access-Control-Allow-Methods: POST
   < X-Content-Type-Options: nosniff
   < X-XSS-Protection: 1; mode=block
   < Cache-Control: no-cache, no-store, max-age=0, must-revalidate
   < Pragma: no-cache
   < Expires: 0
   < X-Frame-Options: DENY
   < Referrer-Policy: origin
   < Content-Length: 0
   < Date: Thu, 18 Mar 2021 22:27:09 GMT
   <
      Connection #0 to host localhost left intact    

This had to be an issue with the browser cache, even though it was weird that it was affecting both Firefox and Chrome, both in normal and private sessions.
But then I started to ensure the CURL request was sending exactly the same headers, and I eventually identified the issue.

The browser, whether Firefox or Chrome, was adding a “Access-Control-Request-Headers: x-requested-with” header. And this header requires a response from the server indicating that it will accept a request with this header. And my Spring Security config was not allowing the “x-requested-with” header:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors()
            .and().csrf().disable()
            .authorizeRequests()
                .anyRequest().access("isAuthenticated() or hasIpAddress('127.0.0.1/24') or hasIpAddress('::1')")
            .and().httpBasic().and().headers().referrerPolicy(ReferrerPolicy.ORIGIN);
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.addAllowedOriginPattern(CorsConfiguration.ALL);
        configuration.setAllowedMethods(List.of(CorsConfiguration.ALL));
        configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }    
}

Changing the setAllowedHeaders line to “configuration.setAllowedHeaders(List.of(CorsConfiguration.ALL)); ” eventually fixed the issue.

Fixing angular warning “exceeded maximum budget”

With your Angular application growing, you will bump into the following warning :

WARNING in budgets: bundle initial-es2015 exceeded maximum budget. Budget 2 MB was not met by 169 kB with a total of 2.16 MB.

Fixing it is quite easy, simply edit your angular.json file, locate the “budgets” node, and incrase the limit :

"budgets": [
                 {
                   "type": "initial",
                   "maximumWarning": "3mb",
                   "maximumError": "5mb"
                 },
                 {
                   "type": "anyComponentStyle",
                   "maximumWarning": "6kb",
                   "maximumError": "10kb"
                 }
               ]

Obviously, this is only valid if you’re getting just above the limit, or don’t care about application loading time. Because the warning is here for a reason, and you may eventually have to find out why your application is getting heavier, and how to trim it.

Preventing Angular observable leaks

A common Angular beginner error is forgetting to unsubscribe from observables. This leak may not be noticeable at first, but will eventually lead to a sluggish application, or other unexpected behaviours.

One can manually keep track of all subscriptions, in order to properly unsubscribe in ngOnDestroy. However, this is tedious, and error prone.

Depending on your subscription context, there are two solutions that you may like.

For a subscription which is a one shot, you need to retrieve your data, and then fortget it, the RxJS first operator combined if straightforward.

myObservable.pipe(first()).subscribe(...);

If you’re expecting several events, and need to unsubscribe only when the component gets destroyed, then @ngneat/until-destroy, is “a neat way to unsubscribe from observables when the component destroyed

The @UntilDestroy({ arrayName: ‘subscriptions’ }) is a nice way to both explicit the requirement for observables to be unsubsribeds, while minimizing boilerplate code.