I created simple Spring Boot 3.0 web service and configured Spring Security Oauth 2.0 authorization with Google/Github (https://www.wimdeblauwe.com/blog/2023/01/24/using-google-login-with-spring-boot-3-and-thymeleaf/ - example of similar app). Everything seems working and ok until I try to make requests not directly to back end but using front end (react). Then I get CORS exception in Get request with 302 code: "Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource."

I tried to handle this exception for several days for now, tried configurations from different resources I could find: setting beans corsConfigurationSource, securityFilterChain, webMvcConfigurer, implementing Filter, usage of Controller's annotation CrossOrigin. Nothing seems to work. In spring boot debug mode I see that request is sent with cors header: "Redirecting to http://localhost:8080/oauth2/authorization/github" if I have correct cors settings in my security config. But the last log says: "Redirecting to https://github.com/login/oauth/authorize..." and then I see error in browser about absence of Access-Control-Allow-Origin header.

@Configuration
public class WebSecurityConfig {
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(List.of("http://localhost:3000"));
        configuration.setAllowedMethods(List.of("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH"));
        configuration.setAllowedHeaders(List.of("*"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .cors()
            .configurationSource(corsConfigurationSource())
            .and()
            .csrf()
            .disable()
            .authorizeHttpRequests()
            .anyRequest()
            .authenticated()
            .and()
            .oauth2Login();
        return http.build();
    }
}

Has someone managed to solve this problem? I am out of ideas. Maybe this thing isn't working in latest versions?

1

There are 1 best solutions below

7
ch4mp On

My bet is that your Spring application is a REST API you want to secure with OAuth2 access tokens, in which case it is an OAuth2 resource server, not an OAuth2 client like Thymeleaf pages in the article you link and this makes it a completely different use-case.

Responsibilities and security requirements are very different for clients and resource servers:

  • clients need sessions (and CSRF protection), resource servers don't
  • clients store tokens, resource servers don't
  • clients sending requests to resource servers have the responsibility to authorize their requests (set an Authorization header with an access token) when resource servers are responsible for checking if a request is authorized with a valid access token and if it should grant access to the requested resource based on the token the claims (in the token or introspected from it)
  • to have tokens to authorize their requests, clients must choose an OAuth2 flow and talk with the authorization server. When users are involved, this flow is authorization-code which starts with a redirection from the client to the authorization server ("login")

This means that login (and logout) are responsibility of the client, not of the resource server.

REST API should be configured as http.oauth2ResourceServer()..., not as client like in your conf (implied by your dependencies and oauth2Login). The dependency to pull is spring-boot-starter-oauth2-resource-server, not spring-boot-starter-oauth2-client.

oauth2Login should be handled by client, not by resource server. Two options there:

  • you are ok with your front-end Javascript code having access to OAuth2 token, then configure it as OAuth2 client, using an OAuth2 client lib for your JS framework to handle flows (login), token storage, etc. (but this is now discouraged)
  • you want to hide the tokens from JS, then implement the OAuth2 Backend For Frontend pattern where an intermediate OAuth2 client stands on your servers between the JS front-end (secured with sessions on the BFF) and the REST API (resource server secured with OAuth2 access tokens)

I have written tutorials for all those scenarios (resource server, client, BFF, both resource server and Thymeleaf client in the same app, etc.) there.