5
votes

I am using Spring Security's OAuth2 server implementation. I am trying to get the access_token from the servers' /oauth/token endpoint using the OAuth2 "Password" grant type by only supplying username and password and the client id without the client secret. This works fine as long as I provide the client id and the client secret in the Authorization header of my HTTP request like so:

curl -u clientid:clientsecret http://myhost ... -d "grant_type=password&username=user&password=pw&client_id=OAUTH_CLIENT"

Following the advice here: Spring OAuth2 disable HTTP Basic Auth for TokenEndpoint, I managed to disable HTTP Basic authentication for the /auth/token endpoint. But when I tried to get the access_token via cURL like so:

curl http://myhost ... -d "grant_type=password&username=user&password=pw&client_id=OAUTH_CLIENT"

I got a BadCredentialsException and could see the message:

Authentication failed: password does not match stored value

in my servers' log. At this point I was slightly irritated, because it was my understanding that this message only shows up when there's something wrong with the username and/or password, not the client id and/or secret. After additionally supplying the client secret in the cURL command like so:

 curl http://myhost ... -d "grant_type=password&username=user&password=pw&client_id=OAUTH_CLIENT&client_secret=SECRET"

everything was fine again.

So does that mean I have to supply the client secret one way or another to access the /auth/token endpoint?

PS: I am aware of the fact that regarding security it is generally a good idea to protect this endpoint via HTTP Basic authentication, but there are some use cases where one would rather be able to do without.

Edit:

I seem to have found a way to omit the client secret. Here's my OAuth2 server configuration (notice the calls to allowFormAuthenticationForClients() and autoApprove(true)):

@Configuration
@EnableAuthorizationServer
class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    private final AuthenticationManager authenticationManager;

    public OAuth2Config(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(this.authenticationManager);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauth) throws Exception {
        // allows access of /auth/token endpoint without HTTP Basic authentication
        oauth.allowFormAuthenticationForClients();  
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
        .inMemory()
        .withClient("acme")
        .autoApprove(true) // <- allows for client id only
        .authorizedGrantTypes("authorization_code", "refresh_token", "password").scopes("openid");
    }

}

Edit II:

The question here: Spring Security OAuth 2.0 - client secret always required for authorization code grant is very closely related to this one but deals with the OAuth2 grant type "Authorization Code", which results in a different workflow like the one you get with grant type "Password".

4

4 Answers

5
votes

According to the specification (RFC 6749), if the client type of your application is public, a client secret is not required. On the contrary, if the client type is confidential, a client secret is required.

If Spring offers an API to set the client type, try to set the client type to public.

2
votes

Spring Boot's implementation requires that a client-secret be passed in to authenticate. You can however override this by creating a bean of type AuthorizationServerConfigurer and configuring it yourself. This is the link to the documenation...

0
votes

Use basic auth but leave the password empty.

0
votes

In the implementation of AuthorizationServerConfigurerAdapter override configure and set password encoder to raw text encoder (do not use it as a default password encoder!).

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

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

    private PasswordEncoder plainTextPasswordEncoder() {
        return new PasswordEncoder() {
            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return !StringUtils.hasText(encodedPassword) || passwordEncoder.matches(rawPassword, encodedPassword);
            }

            @Override
            public String encode(CharSequence rawPassword) {
                return passwordEncoder.encode(rawPassword);
            }
        };
    }

}

Now, for OAuth client details (in memory or in a database), set the client secret to null. In this case, the client will be treated as public and will not require client_secret parameter. If you set client secret for OAuth client details (e.g. BCrypt hash), then the client will be treated as confidential. It will rely on default password encoder (e.g. BCrypt) and require client_secret parameter to be sent in order to obtain an access token.