-1

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?

dur
  • 15,689
  • 25
  • 79
  • 125
Stifler
  • 1
  • 1

1 Answers1

0

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.
  • you want to hide the tokens from JS, then implement the 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.

ch4mp
  • 6,622
  • 6
  • 29
  • 49
  • Thanks for detailed answer and pointing the directions! I'll return to this question on the next week and explore. – Stifler Mar 17 '23 at 21:39
  • Excuse me. It seems that I really need oauth2 resource server. Now I try resource server to validate access token (opaque) and request to google authorization server fails. I believe the problem is that request is created in SpringOpaqueTokenIntrospector with access token in body not in header. Don't you know how to place access token in Authorization header? Also googled much on the internet but couldn't find the answer. Also don't understand why it places access token in body instead of header by default. – Stifler Mar 28 '23 at 21:48
  • Maybe your OIDC Provider (Google) does not want you to use it as authorization server for your own resource servers (not hosted on Google Cloud). In that case, you'll have to use another OP with "social login" if you want your users to "login with Google". Keycloak is an on premise sample, while Auth0 and Amazon Cognito are SaaS samples. The tutorials linked in my answer provide with configuration instructions for the 3. In the process, you'll get a hand on user management (assign roles for instance) and also the ability to login with additional providers (Github, Facebook, etc.) – ch4mp Mar 28 '23 at 22:02
  • And also, I would not configure a resource server with token introspection (opaque-token) when the authorization server is hosted in the cloud: latency of introspection on a remote server before any request can be processed will be a problem. – ch4mp Mar 28 '23 at 22:08
  • Thanks, ch4mp! Really appreciate your answers. For now I found this solution https://stackoverflow.com/questions/71254326/spring-boot-authorization-server-google-oauth2-openid-connect-should-work-with to write my own OpaqueTokenIntrospector for Google. Now validation works well. Will consider your answers more thorough tomorrow. – Stifler Mar 28 '23 at 22:18
  • So I ended up with my custom OpaqueTokenIntrospector which adds access token to header instead of body for the request to Google Authorization Server to validate token. I also used cors settings from my question to solve CORS problem in my oauth2-resource-server. – Stifler Mar 29 '23 at 21:16