This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Security 6.1.11!

Client Authentication Support

JWT Bearer

Please refer to JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants for further details on JWT Bearer Client Authentication.

The default implementation for JWT Bearer Client Authentication is NimbusJwtClientAuthenticationParametersConverter, which is a Converter that customizes the Token Request parameters by adding a signed JSON Web Token (JWS) in the client_assertion parameter.

The java.security.PrivateKey or javax.crypto.SecretKey used for signing the JWS is supplied by the com.nimbusds.jose.jwk.JWK resolver associated with NimbusJwtClientAuthenticationParametersConverter.

Authenticate using private_key_jwt

Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-authentication-method: private_key_jwt
            authorization-grant-type: authorization_code
            ...

The following example shows how to configure DefaultAuthorizationCodeTokenResponseClient:

  • Java

  • Kotlin

Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
	if (clientRegistration.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.PRIVATE_KEY_JWT)) {
		// Assuming RSA key type
		RSAPublicKey publicKey = ...
		RSAPrivateKey privateKey = ...
		return new RSAKey.Builder(publicKey)
				.privateKey(privateKey)
				.keyID(UUID.randomUUID().toString())
				.build();
	}
	return null;
};

OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter =
		new OAuth2AuthorizationCodeGrantRequestEntityConverter();
requestEntityConverter.addParametersConverter(
		new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));

DefaultAuthorizationCodeTokenResponseClient tokenResponseClient =
		new DefaultAuthorizationCodeTokenResponseClient();
tokenResponseClient.setRequestEntityConverter(requestEntityConverter);
val jwkResolver: Function<ClientRegistration, JWK> =
    Function<ClientRegistration, JWK> { clientRegistration ->
        if (clientRegistration.clientAuthenticationMethod.equals(ClientAuthenticationMethod.PRIVATE_KEY_JWT)) {
            // Assuming RSA key type
            var publicKey: RSAPublicKey
            var privateKey: RSAPrivateKey
            RSAKey.Builder(publicKey) = //...
                .privateKey(privateKey) = //...
                .keyID(UUID.randomUUID().toString())
                .build()
        }
        null
    }

val requestEntityConverter = OAuth2AuthorizationCodeGrantRequestEntityConverter()
requestEntityConverter.addParametersConverter(
    NimbusJwtClientAuthenticationParametersConverter(jwkResolver)
)

val tokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
tokenResponseClient.setRequestEntityConverter(requestEntityConverter)

Authenticate using client_secret_jwt

Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            client-authentication-method: client_secret_jwt
            authorization-grant-type: client_credentials
            ...

The following example shows how to configure DefaultClientCredentialsTokenResponseClient:

  • Java

  • Kotlin

Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
	if (clientRegistration.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.CLIENT_SECRET_JWT)) {
		SecretKeySpec secretKey = new SecretKeySpec(
				clientRegistration.getClientSecret().getBytes(StandardCharsets.UTF_8),
				"HmacSHA256");
		return new OctetSequenceKey.Builder(secretKey)
				.keyID(UUID.randomUUID().toString())
				.build();
	}
	return null;
};

OAuth2ClientCredentialsGrantRequestEntityConverter requestEntityConverter =
		new OAuth2ClientCredentialsGrantRequestEntityConverter();
requestEntityConverter.addParametersConverter(
		new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));

DefaultClientCredentialsTokenResponseClient tokenResponseClient =
		new DefaultClientCredentialsTokenResponseClient();
tokenResponseClient.setRequestEntityConverter(requestEntityConverter);
val jwkResolver = Function<ClientRegistration, JWK?> { clientRegistration: ClientRegistration ->
    if (clientRegistration.clientAuthenticationMethod == ClientAuthenticationMethod.CLIENT_SECRET_JWT) {
        val secretKey = SecretKeySpec(
            clientRegistration.clientSecret.toByteArray(StandardCharsets.UTF_8),
            "HmacSHA256"
        )
        OctetSequenceKey.Builder(secretKey)
            .keyID(UUID.randomUUID().toString())
            .build()
    }
    null
}

val requestEntityConverter = OAuth2ClientCredentialsGrantRequestEntityConverter()
requestEntityConverter.addParametersConverter(
    NimbusJwtClientAuthenticationParametersConverter(jwkResolver)
)

val tokenResponseClient = DefaultClientCredentialsTokenResponseClient()
tokenResponseClient.setRequestEntityConverter(requestEntityConverter)

Customizing the JWT assertion

The JWT produced by NimbusJwtClientAuthenticationParametersConverter contains the iss, sub, aud, jti, iat and exp claims by default. You can customize the headers and/or claims by providing a Consumer<NimbusJwtClientAuthenticationParametersConverter.JwtClientAuthenticationContext<T>> to setJwtClientAssertionCustomizer(). The following example shows how to customize claims of the JWT:

  • Java

  • Kotlin

Function<ClientRegistration, JWK> jwkResolver = ...

NimbusJwtClientAuthenticationParametersConverter<OAuth2ClientCredentialsGrantRequest> converter =
		new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver);
converter.setJwtClientAssertionCustomizer((context) -> {
	context.getHeaders().header("custom-header", "header-value");
	context.getClaims().claim("custom-claim", "claim-value");
});
val jwkResolver = ...

val converter: NimbusJwtClientAuthenticationParametersConverter<OAuth2ClientCredentialsGrantRequest> =
    NimbusJwtClientAuthenticationParametersConverter(jwkResolver)
converter.setJwtClientAssertionCustomizer { context ->
    context.headers.header("custom-header", "header-value")
    context.claims.claim("custom-claim", "claim-value")
}