spring-bootspring-securityoauth-2.0

Spring Boot 3.X, Spring security 6 and Oauth 2 - client authorization - auth code not processed


I've been trying to use Spring Boot 3.3.3 with Spring security 6.4.2 to get authorization from a user in a third party server (not Google, Github, etc.), declared server below, and to get some developer's data that the user granted permissions for.

My flow is the following:

  1. The user is redirected to the server's login page.
  2. They log in.
  3. They immediately get authorization request from us
  4. I get back the server's auth code (pretty standard flow up to here)
  5. I need an access and refresh tokens to get data that I was authorized to. Note that from this point I don't need the user to be logged in anymore.

From Spring terminology, I assume this workflow falls in the case of Authorized Client

I expect to have to rewrite some logic, however, Spring fails after point 4 to get an access token from the server. I successfully did so by Postman, once I got the auth code, however my client didn't succeed to do it, and I expect this should be standard OAuth2 framework operation.

application.yml :

   spring:
      application:
        name: glorious-name
      security:
        oauth2:
          client:
            registration:
              cool-client:
                provider:                     server
                client-id:                    cool-client-s-app-id
                client-secret:                cool-client-s-app-secret
                authorization-grant-type:     authorization_code
                redirect-uri:                 http://localhost:8080/oauth2/authorization/cool-client
                client-authentication-method: client_credentials
                scope:                        scopes_that_matter
                state:                        true
            provider:
              server:
                authorization-uri:            https://server.com/oauth/oauth2/auth
                token-uri:                    https://server.com/oauth/oauth2/token

Execution result - note that the auth code arrives to /oauth2/authorization/cool-client but it does not get redirected to the token endpoint as it should. My Controller doesn't get hit at all, or at least no logs about it. Is there a hidden error?

417+02:00 DEBUG 232660 --- [glorious-name] [nio-8581-exec-1] [hash1] o.s.s.web.DefaultRedirectStrategy        : Redirecting to http://localhost:8080/oauth2/authorization/cool-client
424+02:00 DEBUG 232660 --- [glorious-name] [nio-8581-exec-2] [hash2] o.s.security.web.FilterChainProxy        : Securing GET /oauth2/authorization/cool-client
431+02:00 DEBUG 232660 --- [glorious-name] [nio-8581-exec-2] [hash2] o.s.s.web.DefaultRedirectStrategy        : Redirecting to https://server.com/oauth/oauth2/auth?response_type=code&client_id=cool-client-id&scope=encoded_scopes&state=...D&redirect_uri=http://localhost:8080/oauth2/authorization/cool-client
540+02:00 DEBUG 232660 --- [glorious-name] [nio-8581-exec-3] [hash3] o.s.security.web.FilterChainProxy        : Securing GET /oauth2/authorization/cool-client?code=...&scope=encoded_scopes&state=...
544+02:00 DEBUG 232660 --- [glorious-name] [nio-8581-exec-3] [hash3] o.s.s.web.DefaultRedirectStrategy        : Redirecting to https://server.com/oauth/oauth2/auth?response_type=code&client_id=cool_client_id&scope=encoded_scopes&state=...&redirect_uri=http://localhost:8080/oauth2/authorization/cool-client

Here is my debug controller, I have tried all variants from the documentation.

    @GetMapping("/oauth2/authorization/cool-client")
// public ResponseEntity<String> exchangeCodeForToken(@RequestParam(name = "code") String code, @RequestParam(name = "state") String state, @RequestParam(name = "scope") String scope) {
// public ResponseEntity<String> exchangeCodeForToken(@RegisteredOAuth2AuthorizedClient("cool-client") OAuth2AuthorizedClient authorizedClient) {
public ResponseEntity<String> exchangeCodeForToken(@AuthenticationPrincipal OAuth2User user) {

    // log.info("!!!!! SUCCESS !!!!!" + authorizedClient.getClientRegistration().getRegistrationId());
    log.info("USER " + user.getAttributes());
    // log.info("!!!!! THE CODE!!!!! from authorization/lh"  + code);

    return ResponseEntity.ok("Successfully reading the auth code");
}

And SecurityConfig.java (of course I tried customizations, but the problem remains):

@Slf4j
@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
            .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers("/", "/error", "/login/**", "/oauth2/**").permitAll()
                    .anyRequest().authenticated()
            )
            .oauth2Login(oauth -> oauth
                    .failureUrl("/oauth2/error")
            )
            .oauth2Client(Customizer.withDefaults());

    return http.build();
}

}

I started writing the call for the access token myself, however, this feels wrong. What modification/configuration do I need to modify, in order to get the right authentication pattern?

Or can someone confirm that in my case I need to rewrite the logic of getting the access token myself? Any fancy debug/tracing hints? ( I already use TRACE logging level, Postman and the browser's webtools`)


Solution

  • I finally succeeded to produce the needed configuration.

    First, I did not need to override the controller's /login/authorization/code/{registrationId} endpoint.

    Next, I had to make slight changes to the application.yml:

    spring:
          application:
            name: glorious-name
          security:
            oauth2:
              client:
                registration:
                  cool-client:
                    provider:                     server
                    client-id:                    cool-client-s-app-id
                    client-secret:                cool-client-s-app-secret
                    authorization-grant-type:     authorization_code
                    redirect-uri:                 http://localhost:8080/login/oauth2/code/cool-client
                    client-authentication-method: client_secret_post
                    scope:                        scopes_that_matter
                    state:                        true
                provider:
                  server:
                    authorization-uri:            https://server.com/oauth/oauth2/auth
                    token-uri:                    https://server.com/oauth/oauth2/token
                    user-info-uri:                https://server.com/user/uri/that/I/had/to/figure/out/on/my/own
                    user-name-attribute:          user_id_attribute_name
    

    Third, it was useful to implement a login success page, so that I know when I'm finally there.

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers( "/error", "/login/**", "/oauth2/**").permitAll()
                        .anyRequest().authenticated()
                )
                .oauth2Login(oauth -> oauth
                        .defaultSuccessUrl("/user/profile")
                )
                .oauth2Client(Customizer.withDefaults());
    
        return http.build();
    }
    

    On the /user/profile/ endpoint I added an authorized call to the API that looked like this.

    @GetMapping("/user/profile")
    public ResponseEntity<String> getUserProfile() {
    
        String resourceUri = <user_profile_uri>;
        ResponseEntity<Object> body = this.restClient.get()
                .uri(resourceUri)
                .header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
                .retrieve()
                .toEntity(Object.class);
    
        return ResponseEntity.ok(body.toString());
    }
    

    I hope this is useful to someone, it costed me a lot of headache to fix.