Tag Archives: REST

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.

Wattpad API requests must contain a valid browser User-Agent header

Recently, I’ve been playing with the Wattpad API. I was interested in collecting stats about the most popular publications.
Wattpad_logo_svg
Even though the API is simple and well documented, all attempts to use it through a python script failed. Requests were rejected with a “403 Access forbidden” HTTP error. My first intuition was that the basic authentication was incorrectly specified, but despite the API samples, I couldn’t figure out what was wrong with it.

Using WireShark to sniff the API sample requests would have helped solving the issue, but since the API only supports https, sniffing wasn’t an option (I later realized that I could have simply disabled https, just to sniff the request, since I didn’t care about the response).

Eventually, the problem came from the User-Agent header. The python script used a default User-Agent, which was rejected by the Wattpad server. Impersonating a legitimate browser (ex : chrome) solved the issue, requests were immediately accepted.

Below is a Python sample.
Continue reading