spring-projects/spring-security · error · OAuth2AuthenticationException
INVALID_CLIENT
INVALID_CLIENT
Error message
Failed to find a Signature Verifier for Client: '<registeredClient.getId()>'. Check to ensure you have configured the JWK Set URL.
What it means
In the OAuth2 Authorization Server, when a client authenticates with a JWT client assertion signed with an asymmetric algorithm (e.g. RS256), the server builds a JwtDecoder that fetches the client's public keys via a JWK Set URL. JwtClientAssertionDecoderFactory.buildDecoder throws this INVALID_CLIENT error when the client's JwkSetUrl client setting is missing or blank, so no signature verifier can be constructed.
Source
Thrown at oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionDecoderFactory.java:144
* @param jwtValidatorFactory the factory that provides an
* {@link OAuth2TokenValidator} for the specified {@link RegisteredClient}
*/
public void setJwtValidatorFactory(Function<RegisteredClient, OAuth2TokenValidator<Jwt>> jwtValidatorFactory) {
Assert.notNull(jwtValidatorFactory, "jwtValidatorFactory cannot be null");
this.jwtValidatorFactory = jwtValidatorFactory;
}
private static NimbusJwtDecoder buildDecoder(RegisteredClient registeredClient) {
JwsAlgorithm jwsAlgorithm = registeredClient.getClientSettings()
.getTokenEndpointAuthenticationSigningAlgorithm();
if (jwsAlgorithm instanceof SignatureAlgorithm) {
String jwkSetUrl = registeredClient.getClientSettings().getJwkSetUrl();
if (!StringUtils.hasText(jwkSetUrl)) {
OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
"Failed to find a Signature Verifier for Client: '" + registeredClient.getId()
+ "'. Check to ensure you have configured the JWK Set URL.",
JWT_CLIENT_AUTHENTICATION_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error);
}
return NimbusJwtDecoder.withJwkSetUri(jwkSetUrl)
.jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm)
.restOperations(restTemplate)
.build();
}
if (jwsAlgorithm instanceof MacAlgorithm) {
String clientSecret = registeredClient.getClientSecret();
if (!StringUtils.hasText(clientSecret)) {
OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
"Failed to find a Signature Verifier for Client: '" + registeredClient.getId()
+ "'. Check to ensure you have configured the client secret.",
JWT_CLIENT_AUTHENTICATION_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error);
}
SecretKeySpec secretKeySpec = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8),
JCA_ALGORITHM_MAPPINGS.get(jwsAlgorithm));
return NimbusJwtDecoder.withSecretKey(secretKeySpec).macAlgorithm((MacAlgorithm) jwsAlgorithm).build();View on GitHub (pinned to 96852e8860)
Solutions
- Set the JWK Set URL on the client: RegisteredClient.withClient(id).clientSettings(ClientSettings.builder().jwkSetUrl("https://client.example.com/jwks").build()).build()
- Verify the stored client record actually contains a non-empty jwkSetUrl (check DB/claim source if clients are loaded dynamically).
- Alternatively configure the client's JWK Set URI through your RegisteredClientRepository registration code path used at authorization time.
- Confirm the client is actually using private_key_jwt and that the intended auth method matches its registration (token_endpoint_authentication_method).
Example fix
// before
RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client-a")
.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
.build();
// after
RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client-a")
.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
.clientSettings(ClientSettings.builder()
.jwkSetUrl("https://client-a.example.com/jwks")
.build())
.build(); Defensive patterns
Strategy: validation
Validate before calling
if (client.getClientSettings() == null ||
!StringUtils.hasText(client.getClientSettings().getJwkSetUrl())) {
throw new IllegalStateException(
"Client " + client.getId() + " uses private_key_jwt but has no jwkSetUrl configured");
} Try / catch
try {
// token request with private_key_jwt
} catch (OAuth2AuthenticationException ex) {
if (OAuth2ErrorCodes.INVALID_CLIENT.equals(ex.getError().getErrorCode())) {
log.error("Client assertion rejected: {}. Verify jwkSetUrl registration.",
ex.getError().getDescription());
}
throw ex;
} Prevention
- Always pair ClientAuthenticationMethod.PRIVATE_KEY_JWT with ClientSettings.jwkSetUrl in your client registration factory
- Add a startup test that requests tokens for every registered client
- If clients come from a database, validate jwkSetUrl non-null on load
When it happens
Trigger: A client sends a private_key_jwt client assertion, but RegisteredClient.getClientSettings().getJwkSetUrl() was never set (or is empty string/whitespace) on the server for that client, so buildDecoder fails before decoding the assertion.
Common situations: Registering a client for client_secret_jwt/private_key_jwt auth without the jwk-set-url client setting; loading clients from a database where the JWK Set URL column is null; copying a client registration from a symmetric-secret example; typos in settings builder calls.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/7f5d43e287b04297.
Report an issue: GitHub.