2

Trying to implement the OAuth2 protocol using Spring Authorization Server. Created a simple application with the following configuration.

@SpringBootApplication
class AuthorizationServerApplication

fun main(args: Array<String>) {
    runApplication<AuthorizationServerApplication>(*args)
}
@Configuration
@Import(OAuth2AuthorizationServerConfiguration::class)
class AuthorizationServerConfig {

    @Bean
    fun registeredClientRepository(): RegisteredClientRepository? {
        val registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
            .clientId("client")
            .clientSecret("{noop}client-secret")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
            .authorizationGrantType(AuthorizationGrantType.PASSWORD)
            .redirectUri("http://example-host:9002/test/admin")
            .redirectUri("http://example-host:9002/test")
            .scope(OidcScopes.OPENID)
            .scope("read")
            .build()
        return InMemoryRegisteredClientRepository(registeredClient)
    }

    ...

    @Bean
    fun providerSettings(): ProviderSettings? {
        return ProviderSettings.builder()
            .issuer("http://example-host:9000")
            .build()
    }
}
@EnableWebSecurity
class DefaultSecurityConfig {

    @Bean
    fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain? {
        http
            .authorizeRequests { authorizeRequests ->
                authorizeRequests
                    .anyRequest().permitAll()
            }
            // these are disabled so that I won't get any additional issue this needs to be changed
            .formLogin().disable()
            .csrf().disable()

        return http.build()
    }

    @Bean
    fun users(): UserDetailsService? {
        val admin: UserDetails = User.withDefaultPasswordEncoder()
            .username("admin")
            .password("password")
            .roles("ADMIN")
            .authorities("read", "write")
            .build()

        val user: UserDetails = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .authorities("read")
            .build()
        return InMemoryUserDetailsManager(admin, user)
    }
}

When calling the following endpoint: GET http://example-host:9000/.well-known/oauth-authorization-server I get back these:

{
    "issuer": "http://example-host:9000",
    "authorization_endpoint": "http://example-host:9000/oauth2/authorize",
    "token_endpoint": "http://example-host:9000/oauth2/token",
    "token_endpoint_auth_methods_supported": [
        "client_secret_basic",
        "client_secret_post",
        "client_secret_jwt",
        "private_key_jwt"
    ],
    "jwks_uri": "http://example-host:9000/oauth2/jwks",
    "response_types_supported": [
        "code"
    ],
    "grant_types_supported": [
        "authorization_code",
        "client_credentials",
        "refresh_token"
    ],
    "revocation_endpoint": "http://example-host:9000/oauth2/revoke",
    "revocation_endpoint_auth_methods_supported": [
        "client_secret_basic",
        "client_secret_post",
        "client_secret_jwt",
        "private_key_jwt"
    ],
    "introspection_endpoint": "http://example-host:9000/oauth2/introspect",
    "introspection_endpoint_auth_methods_supported": [
        "client_secret_basic",
        "client_secret_post",
        "client_secret_jwt",
        "private_key_jwt"
    ],
    "code_challenge_methods_supported": [
        "S256"
    ]
}

I'm trying to go past authentication and trying to follow this documentation. I tried multiple calls one of these is:

curl --location --request POST 'example-host:9000/oauth2/token' \
--header 'Authorization: Basic YWRtaW46cGFzc3dvcmQ=' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=client' \
--data-urlencode 'client_secret=client-secret'

I get back 401 most of the time with different messages. Can't really figure out where I can find some documentation with examples as the examples that I was able to find are not really helpful for my usecase. I don't fully get how I would be able to authenticate and use the resource servers' endpoints in case of the client being a front-end application. Maybe I misunderstood something?

Edit: Adding trace logs from authorization server when requesting token endpoint:

curl --location --request POST 'example-host:9000/oauth2/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=client' \
--data-urlencode 'client_secret=client-secret' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=user' \
--data-urlencode 'password=password'
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy        : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer$$Lambda$746/0x000000080105b608@7a764446, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@841f2ce, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@38eb32b, org.springframework.security.web.context.SecurityContextPersistenceFilter@4232bd1e, org.springframework.security.oauth2.server.authorization.web.ProviderContextFilter@1c463b0b, org.springframework.security.web.header.HeaderWriterFilter@6e87e57e, org.springframework.security.web.csrf.CsrfFilter@3657ca3e, org.springframework.security.web.authentication.logout.LogoutFilter@36c9161d, org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter@c85053, org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter@827dabb, org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter@3fce33c2, org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter@5f5e39a5, org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter@6f53bec6, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@1be28be5, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@12322dee, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@4aba4a37, org.springframework.security.web.session.SessionManagementFilter@3c29c1e5, org.springframework.security.web.access.ExceptionTranslationFilter@5e60456e, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@5771f1b4, org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter@1ec08e27, org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter@548d0a8d, org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter@6c15398a, org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter@6bcb4915]] (1/2)
2022-06-14 16:41:47.154 DEBUG 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy        : Securing POST /oauth2/token
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy        : Invoking DisableEncodeUrlFilter (1/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy        : Invoking WebAsyncManagerIntegrationFilter (2/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy        : Invoking SecurityContextPersistenceFilter (3/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] w.c.HttpSessionSecurityContextRepository : Created SecurityContextImpl [Null authentication]
2022-06-14 16:41:47.154 DEBUG 34744 --- [nio-9000-exec-2] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy        : Invoking ProviderContextFilter (4/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy        : Invoking HeaderWriterFilter (5/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy        : Invoking CsrfFilter (6/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.csrf.CsrfFilter         : Did not protect against CSRF since request did not match And [CsrfNotRequired [TRACE, HEAD, GET, OPTIONS], Not [Or [org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer$$Lambda$746/0x000000080105b608@7a764446]]]
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy        : Invoking LogoutFilter (7/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.s.w.a.logout.LogoutFilter            : Did not match request to Ant [pattern='/logout', POST]
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy        : Invoking OAuth2AuthorizationEndpointFilter (8/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy        : Invoking OidcProviderConfigurationEndpointFilter (9/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy        : Invoking NimbusJwkSetEndpointFilter (10/22)
2022-06-14 16:41:47.155 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy        : Invoking OAuth2AuthorizationServerMetadataEndpointFilter (11/22)
2022-06-14 16:41:47.155 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy        : Invoking OAuth2ClientAuthenticationFilter (12/22)
2022-06-14 16:41:47.155 TRACE 34744 --- [nio-9000-exec-2] o.s.s.authentication.ProviderManager     : Authenticating request with JwtClientAssertionAuthenticationProvider (1/11)
2022-06-14 16:41:47.155 TRACE 34744 --- [nio-9000-exec-2] o.s.s.authentication.ProviderManager     : Authenticating request with ClientSecretAuthenticationProvider (2/11)
2022-06-14 16:41:47.155 TRACE 34744 --- [nio-9000-exec-2] o.s.s.authentication.ProviderManager     : Authenticating request with PublicClientAuthenticationProvider (3/11)
2022-06-14 16:41:47.155 TRACE 34744 --- [nio-9000-exec-2] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match request to [Is Secure]
2022-06-14 16:41:47.155 DEBUG 34744 --- [nio-9000-exec-2] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2022-06-14 16:41:47.156 DEBUG 34744 --- [nio-9000-exec-2] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2022-06-14 16:41:47.156 DEBUG 34744 --- [nio-9000-exec-2] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
GROX13
  • 4,605
  • 4
  • 27
  • 41
  • 1
    I think you get confused with the users and the client. You should use the client's user & password (i.e., 'client:client-secret') when calling the server, instead of the user's credentials. – yoni Jun 14 '22 at 11:21
  • [yoni](https://stackoverflow.com/users/504807/yoni) I tried that as well I get the same response. `{ "error": "invalid_client" }` – GROX13 Jun 14 '22 at 11:50
  • 1
    See [Default Configuration](https://docs.spring.io/spring-authorization-server/docs/current/reference/html/configuration-model.html#default-configuration) in the docs, which mentions `"The authorization_code grant requires the resource owner to be authenticated. Therefore, a user authentication mechanism must be configured in addition to the default OAuth2 security configuration."`. I would start with the [Getting Started](https://docs.spring.io/spring-authorization-server/docs/current/reference/html/getting-started.html#developing-your-first-application) example. – Steve Riesenberg Jun 14 '22 at 22:24
  • 1
    Also, it sounds like you're looking to build a public client, which cannot authenticate with the server, so you'll need to enable PKCE. You may be interested in [this example](https://github.com/sjohnr/spring-authorization-server/blob/b7ad8f2468830d24cf34266974b5b48b5a3bd2a7/samples/default-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java#L82-L92) for configuring support for a public client and [this webinar](https://tanzu.vmware.com/content/webinars/mar-10-getting-started-with-spring-authorization-server) for more info on that example. – Steve Riesenberg Jun 14 '22 at 22:28
  • Thank you [Steve Riesenberg](https://stackoverflow.com/users/15835039/steve-riesenberg)! You are right I was looking for something like a public client and just wanted to make sure I understood how this implementation worked first that's why I started with this example. – GROX13 Jun 15 '22 at 17:35

2 Answers2

2

First, in your case you don't need the Authorization header in your request for token since you explicitly allowed all requests to pass through via authorizeRequests.anyRequest().permitAll().

Second, in your curl request you didn't specify at least a desired grant type and its parameters.

For example, for the password grant type the request might look something like this:

curl -L -X POST 'example-host:9000/oauth2/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=client' \
--data-urlencode 'client_secret=client-secret' \ 
--data-urlencode 'grant_type=password' \ 
--data-urlencode 'username=user' \ 
--data-urlencode 'password=password'

UPDATE:

  1. Spring authorization server 0.3.0 doesn't support the password grant type, exactly as it shows in the grant_types_supported section of the .well-known/oauth-authorization-server endpoint output. There's just no such authentication provider in the org.springframework.security.oauth2.server.authorization.authentication package.

  2. To make at least the client_credentials token request work, add

.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)

and (in case you'd like to pass the client_id and client_secret inside the POST body)

.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)

to your RegisteredClient in the registeredClientRepository. Then this can be tested with

curl -L -X POST 'http://example-host:9000/oauth2/token' -H 'Content-Type: application/x-www-form-urlencoded' -d 'grant_type=client_credentials&client_id=client&client_secret=client-secret'

(be sure to pass the actual client_id and client_secret of a RegisteredClient)

Also, if you import OAuth2AuthorizationServerConfiguration a default SecurityFilterChain for the auth server endpoints is created and there's no need to define it manually. On the other hand a SecurityFilterChain for your app authentication is likely still needed.

  1. To debug the OAuth authentication process and see the exact exceptions, if any, set some breakpoints in the org.springframework.security.authentication.ProviderManager#authenticate() method
dekkard
  • 6,121
  • 1
  • 16
  • 26
  • Thank you [dekkard](https://stackoverflow.com/users/4571544/dekkard) I tried that as well exactly the same response `invalid_client` will add trace logs to my question as well. – GROX13 Jun 14 '22 at 12:43
  • Please set `debug=true` to your application properties and add the full log of your request being processed. The one you added shows no indication of an error. – dekkard Jun 14 '22 at 13:02
  • [dekkard](https://stackoverflow.com/users/4571544/dekkard) I have these 2 properties set `logging.level.org.springframework=DEBUG logging.level.org.springframework.security=TRACE` in application properties and created [Gist with full application log](https://gist.github.com/GROX13/96f9030a2398fd263185ad508dbef8fb) I don't think there would be anything additional. I started the application and called the token endpoint once. – GROX13 Jun 14 '22 at 13:24
  • Just noticed that the output of `.well-known/oauth-authorization-server` shows no `password` grant type in the `grant_types_supported` section for some reason. Try the `client_credentials` grant type, just to check if your configuration works at all: `curl -L -X POST 'example-host:9000/oauth2/token' -H 'Content-Type: application/x-www-form-urlencoded' --data-urlencode 'grant_type=client_credentials' --data-urlencode 'client_id=client' --data-urlencode 'client_secret=client-secret'` – dekkard Jun 15 '22 at 07:22
  • I still get `{ "error": "invalid_client" }`. I tried different grant types as well and can't really find anything useful with all my experiments. – GROX13 Jun 15 '22 at 08:12
  • Can you share a minimal project with the configuration in question that can be used to reproduce this issue? – dekkard Jun 15 '22 at 08:55
  • [dekkard](https://stackoverflow.com/users/4571544/dekkard) sure will push on GitHub as soon as I'll have chance – GROX13 Jun 15 '22 at 18:02
  • [dekkard](https://stackoverflow.com/users/4571544/dekkard) adding link with GitHub repo https://github.com/GROX13/spring-authorization-server-demo – GROX13 Jun 15 '22 at 18:44
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/245645/discussion-between-grox13-and-dekkard). – GROX13 Jun 15 '22 at 19:09
0

your client id and client secret must be on Base64 format.

String hashed_keys = Base64.encode("client_id:client_secret");

and you must pass it as http header

curl -L -X POST 'http://example-host:9000/oauth2/token' -H "Authorisation: hashed_keys" -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=client_credentials'