12
votes

According to the spec, requests for a token using the authorization code grant are not required to be authenticated as long as the client_id is included in the request and the client_id is the same one used to generate the code. However, with the Spring Security OAuth 2.0 implementation, it appears that basic auth is always required on the /oauth/token endpoint even if the client was never assigned a secret.

It looks like there is support for allowing clients without a secret due to the isSecretRequired() method in the ClientDetails interface. What do I need to do to enable clients without a secret to be authenticated at the /oauth/token URL?

4.1.3. Access Token Request

The client makes a request to the token endpoint by sending the
following parameters using the "application/x-www-form-urlencoded"
format per Appendix B with a character encoding of UTF-8 in the HTTP
request entity-body:

grant_type REQUIRED. Value MUST be set to "authorization_code".

code REQUIRED. The authorization code received from the authorization server.

redirect_uri REQUIRED, if the "redirect_uri" parameter was included in the authorization request as described in Section 4.1.1, and their values MUST be identical.

client_id REQUIRED, if the client is not authenticating with the authorization server as described in Section 3.2.1.

If the client type is confidential or the client was issued client credentials (or assigned other authentication requirements), the
client MUST authenticate with the authorization server as described
in Section 3.2.1.

6

6 Answers

11
votes

Authenticating the client using the form parameters instead of basic auth is enabled using the allowFormAuthenticationForClients() method as shown in the code sample below.

class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    @Override
    void configure(AuthorizationServerSecurityConfigurer security) {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients()
    }
}

The allowFormAuthenticationForClients() method triggers the addition of the ClientCredentialsTokenEndpointFilter which allows for authentication via form parameters.

7
votes

Spring allows you to define OAuth2 clients with an empty secret. These can be considered "public" clients, or clients that are unable to keep a secret. Think of Javascript apps, mobile apps, ..... You typically don't want to have client secrets stored there.

As you point out, according to the OAuth2 spec, a token endpoint can opt to not require a secret for these public clients.

So in Spring, simply define an OAuth2 client with an empty secret, and configure it for a limited set of grant types (authorization_code and refresh_token)

The Spring Security implementation token url will accept a token exchange without a client secret for that particular OAuth2 client.

1
votes

In order to solve the issue see the method loadUserByUsername of class: org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService:

if (clientSecret == null || clientSecret.trim().length() == 0) {
   clientSecret = this.emptyPassword;
}

Probably in your case emptyPassword has not been initialized with empty encoded password by your password encoder. Set missing password encoder in AuthorizationServerConfigurerAdapter:

@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
    oauthServer.passwordEncoder(passwordEncoder);
}
0
votes

Initially I had a similar setup to the accepted answer, which is definitely a prerequisite to make this work. But what is missing is that you cannot simply set the password to null. You must set it to an empty password, for example like this:

String secret = PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("");
clientDetails.setClientSecret(secret);

If you don't do this, you will still get a 401!

0
votes

This worked for me

    @Override
    public void configure(AuthorizationServerSecurityConfigurer cfg) throws Exception {
        cfg
            .tokenKeyAccess("permitAll()")
            .checkTokenAccess("isAuthenticated()")
            .allowFormAuthenticationForClients()
            .passwordEncoder(clientPasswordEncoder());
    }


    @Bean("clientPasswordEncoder")
    PasswordEncoder clientPasswordEncoder() {
        return new BCryptPasswordEncoder(4);
    }

Test 1: enter image description here enter image description here

Test 2: enter image description here

-1
votes

like this:

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
            .withClient("client").secret(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("")) //empty
            .authorizedGrantTypes("authorization_code", "refresh_token")
            .redirectUris("http://www.dev.com")
            .scopes("all")
            .autoApprove(true);
} @Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
        throws Exception {
    oauthServer.tokenKeyAccess("permitAll()")
            .checkTokenAccess("isAuthenticated()")
            .allowFormAuthenticationForClients();
}

It is ok.