Search code examples
javaspringspring-bootspring-securityspring-authorization-server

Spring Authorization Server returns access_denied after granting consent


I tried setting up a very minimal Spring Authorization Server example as part of evaluating it for use in one of my projects. The Authorization Server will be used with a SPA application so the Authorization Code flow with PKCE will be used. I am using Spring Boot 3.2.0.

The example application is generated by Spring Initializr and consists of a single configuration file to set up the minimal requirements for an Authorization server that largely follows the "How-To" from the official docs

@SpringBootApplication
@EnableWebSecurity
public class DemoApplication
{
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    public UserDetailsService users() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("admin")
                .password("admin")
                .roles("ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        http
                // Redirect to the login page when not authenticated from the
                // authorization endpoint
                .exceptionHandling((exceptions) -> exceptions
                        .defaultAuthenticationEntryPointFor(
                                new LoginUrlAuthenticationEntryPoint("/login"),
                                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                        )
                );

        return http.cors(Customizer.withDefaults()).build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception
    {
        return httpSecurity
                .authorizeHttpRequests((authorize) -> authorize
                        .requestMatchers("/admin/**").hasRole("ADMIN")
                        .requestMatchers("/", "/**").permitAll()
                )
                .cors(Customizer.withDefaults())
                .formLogin(Customizer.withDefaults())
                .build();
    }

    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        RegisteredClient publicClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("public-client")
                .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUri("http://127.0.0.1:4200")
                .clientSettings(ClientSettings.builder()
                        .requireAuthorizationConsent(true)
                        .requireProofKey(true)
                        .build()
                )
                .build();

        return new InMemoryRegisteredClientRepository(publicClient);
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource()
    {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        config.addAllowedOrigin("http://127.0.0.1:4200");
        config.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}

When making a properly formed request to the /authorize endpoint I am redirected to the default login page as expected. Spring Authorization Server default log in page

For the sake of completeness the example request that redirects me to this login page looks like http://127.0.0.1:8080/oauth2/authorize?response_type=code&client_id=public-client&redirect_uri=http%3A%2F%2Flocalhost%3A4200&state=1234xyz&code_challenge=cFeDr-CjSa6l1C3w388Qoz1GpM3Pa0qzkOF9rq0Wp8A&code_challenge_method=S256 using a code challenge and verifier generated by PKCE Tools

When entering valid user credentials (as configured in the configuration above) I am redirected to the consent screen as expected. Spring Authorization Server consent screen

However, upon granting consent by selecting the "Submit Consent" button I get an "access_denied" error returned in the redirect url. The url that gets redirected to is http://127.0.0.1:4200/?error=access_denied&error_description=OAuth%202.0%20Parameter%3A%20client_id&error_uri=https%3A%2F%2Fdatatracker.ietf.org%2Fdoc%2Fhtml%2Frfc6749%23section-4.1.2.1&state=1234xyz

This includes the error message "access_denied" and error description "OAuth 2.0 Parameter: client_id" and points me to a section of the RFC which does not contain any further useful information for troubleshooting this specific error message.

Ostensibly, the error description indicates that Spring thinks there is something wrong with the client_id but I fail to see what could be wrong with it.

Which piece of the puzzle am I missing here that is causing me to receive this error for a valid user who has successfully authenticated and chosen to give their consent?

EDIT 2024-02-19

I noticed when revisiting this that my request to the authorization server was using 127.0.0.1 while my redirect was using localhost. I have since corrected this so that both the request and redirect use the same exact host and have tested it by trying out both hosts of 127.0.0.1 and localhost. However, the results when using either host in a consistent manner were the same as described in the original question and I am still receiving the "access_denied" error.

The question has been updated to show the use of 127.0.0.1.


Solution

  • I just faced with the same issue, and after some investigations, found that it needed to add additional scope (I had only scope(OidcScopes.OPENID) before) in registeredClientRepository method like

    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        RegisteredClient publicClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("public-client")
                .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUri("http://127.0.0.1:4200")
                .scope("your-scope")
                .scope(OidcScopes.OPENID)
                .clientSettings(ClientSettings.builder()
                        .requireAuthorizationConsent(true)
                        .requireProofKey(true)
                        .build()
                )
                .build();
    

    And then add it to request's parameters as: http://127.0.0.1:8080/oauth2/authorize?response_type=code&client_id=public-client&redirect_uri=http%3A%2F%2Flocalhost%3A4200&state=1234xyz&code_challenge=cFeDr-CjSa6l1C3w388Qoz1GpM3Pa0qzkOF9rq0Wp8A&code_challenge_method=S256&scope=your-scope

    I hope it will help you.