I am following the book
Spring Security in Action, Second Edition Chapter 14.2 Running the authorization code grant type.
I believe that I followed the instructions clearly, but I keep getting an exception
org.springframework.security.oauth2.core.OAuth2AuthenticationException: Client authentication failed: client_id
This is my flow. Fetch the code from the first url, afterwards, I use the code and pass it in my curl request
http://localhost:8080/oauth2/authorize?response_type=code&client_id=client&scope=openid&redirect_uri=https://www.manning.com/authorized&code_challenge=Ys2R6lAx2idjbr_mVPYzweT2loaYVBPvUKBaeu3zDgo&code_challenge_method=S256
curl -X POST 'http://localhost:8080/oauth2/token?client_id=client&redirect_uri=https://www.manning.com/authorized&grant_type=authorization_code&code=lCC4um4ivSSAvXRtldrs8bWZV-Lre7HGOKjFnpifnZMTtZA6FGR7nxVXfNwVYX0koYfX0V7ejU9hm3birOzt_3nO1MAhiBuIx2rcWPQ9YXVqnUuWxGmWVrRfo0Q3gvR2&code_verifier=Uj0Kh6iiJvuEPKQcEnejWB9__bxCY-XwglkymMyXlJo' --header 'Authorization: Basic YmlsbDpwYXNzd29yZA=='
Here is my configuration
@Configuration
public class SecurityConfig {
@Bean
@Order(1)
public SecurityFilterChain asFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration
.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults());
http.exceptionHandling((e) ->
e.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/login"))
);
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http.formLogin(Customizer.withDefaults());
http.authorizeHttpRequests(
c -> c.anyRequest().authenticated()
);
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails userDetails = User.withUsername("bill")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(userDetails);
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
/*
* code_challenge=QYPAZ5NU8yvtlQ…—If using the authorization code enhanced with PKCE
* (discussed in chapter 13), you must provide the code challenge with the authorization request.
* When requesting the token, the client must send the verifier pair to prove they are the same application
* that initially sent this request. The PKCE flow is enabled by default.
* */
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient
.withId(UUID.randomUUID().toString())
.clientId("client")
.clientSecret("secret")
.clientAuthenticationMethod(
ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(
AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("https://www.manning.com/authorized")
.tokenSettings(
TokenSettings.builder()
.accessTokenFormat(OAuth2TokenFormat.REFERENCE)
.accessTokenTimeToLive(Duration.ofHours(24))
.build()
)
.scope(OidcScopes.OPENID)
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
@Bean
public JWKSource<SecurityContext> jwkSource()
throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator =
KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey publicKey =
(RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey =
(RSAPrivateKey) keyPair.getPrivate();
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder()
.build();
}
}
In your RegisteredClientRepository
you set the clientId as client
and clientSecret
as secret
.
In your CURL request you pass:
--header 'Authorization: Basic YmlsbDpwYXNzd29yZA=='
Which when decoded is: bill:password
. This needs to be client:secret
, which is Y2xpZW50OnNlY3JldA==
1