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

Authorization Grant Support

This section describes Spring Security’s support for authorization grants.

Authorization Code

Please refer to the OAuth 2.0 Authorization Framework for further details on the Authorization Code grant.

Obtaining Authorization

Please refer to the Authorization Request/Response protocol flow for the Authorization Code grant.

Initiating the Authorization Request

The OAuth2AuthorizationRequestRedirectWebFilter uses a ServerOAuth2AuthorizationRequestResolver to resolve an OAuth2AuthorizationRequest and initiate the Authorization Code grant flow by redirecting the end-user’s user-agent to the Authorization Server’s Authorization Endpoint.

The primary role of the ServerOAuth2AuthorizationRequestResolver is to resolve an OAuth2AuthorizationRequest from the provided web request. The default implementation DefaultServerOAuth2AuthorizationRequestResolver matches on the (default) path /oauth2/authorization/{registrationId} extracting the registrationId and using it to build the OAuth2AuthorizationRequest for the associated ClientRegistration.

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

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/authorized/okta"
            scope: read, write
        provider:
          okta:
            authorization-uri: https://dev-1234.oktapreview.com/oauth2/v1/authorize
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

A request with the base path /oauth2/authorization/okta will initiate the Authorization Request redirect by the OAuth2AuthorizationRequestRedirectWebFilter and ultimately start the Authorization Code grant flow.

The AuthorizationCodeReactiveOAuth2AuthorizedClientProvider is an implementation of ReactiveOAuth2AuthorizedClientProvider for the Authorization Code grant, which also initiates the Authorization Request redirect by the OAuth2AuthorizationRequestRedirectWebFilter.

If the OAuth 2.0 Client is a Public Client, then configure the OAuth 2.0 Client registration as follows:

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-authentication-method: none
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/authorized/okta"
            # ...

Public Clients are supported using Proof Key for Code Exchange (PKCE). If the client is running in an untrusted environment (eg. native application or web browser-based application) and therefore incapable of maintaining the confidentiality of it’s credentials, PKCE will automatically be used when the following conditions are true:

  1. client-secret is omitted (or empty)

  2. client-authentication-method is set to "none" (ClientAuthenticationMethod.NONE)

If the OAuth 2.0 Provider supports PKCE for Confidential Clients, you may (optionally) configure it using DefaultServerOAuth2AuthorizationRequestResolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce()).

The DefaultServerOAuth2AuthorizationRequestResolver also supports URI template variables for the redirect-uri using UriComponentsBuilder.

The following configuration uses all the supported URI template variables:

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            # ...
            redirect-uri: "{baseScheme}://{baseHost}{basePort}{basePath}/authorized/{registrationId}"
            # ...

{baseUrl} resolves to {baseScheme}://{baseHost}{basePort}{basePath}

Configuring the redirect-uri with URI template variables is especially useful when the OAuth 2.0 Client is running behind a Proxy Server. This ensures that the X-Forwarded-* headers are used when expanding the redirect-uri.

Customizing the Authorization Request

One of the primary use cases a ServerOAuth2AuthorizationRequestResolver can realize is the ability to customize the Authorization Request with additional parameters above the standard parameters defined in the OAuth 2.0 Authorization Framework.

For example, OpenID Connect defines additional OAuth 2.0 request parameters for the Authorization Code Flow extending from the standard parameters defined in the OAuth 2.0 Authorization Framework. One of those extended parameters is the prompt parameter.

The prompt parameter is optional. Space delimited, case sensitive list of ASCII string values that specifies whether the Authorization Server prompts the End-User for re-authentication and consent. The defined values are: none, login, consent, and select_account.

The following example shows how to configure the DefaultServerOAuth2AuthorizationRequestResolver with a Consumer<OAuth2AuthorizationRequest.Builder> that customizes the Authorization Request for oauth2Login(), by including the request parameter prompt=consent.

  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {

	@Autowired
	private ReactiveClientRegistrationRepository clientRegistrationRepository;

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			.authorizeExchange(authorize -> authorize
				.anyExchange().authenticated()
			)
			.oauth2Login(oauth2 -> oauth2
				.authorizationRequestResolver(
					authorizationRequestResolver(this.clientRegistrationRepository)
				)
			);
		return http.build();
	}

	private ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver(
			ReactiveClientRegistrationRepository clientRegistrationRepository) {

		DefaultServerOAuth2AuthorizationRequestResolver authorizationRequestResolver =
				new DefaultServerOAuth2AuthorizationRequestResolver(
						clientRegistrationRepository);
		authorizationRequestResolver.setAuthorizationRequestCustomizer(
				authorizationRequestCustomizer());

		return  authorizationRequestResolver;
	}

	private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
		return customizer -> customizer
					.additionalParameters(params -> params.put("prompt", "consent"));
	}
}
@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

    @Autowired
    private lateinit var customClientRegistrationRepository: ReactiveClientRegistrationRepository

    @Bean
    fun securityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        http {
            authorizeExchange {
                authorize(anyExchange, authenticated)
            }
            oauth2Login {
                authorizationRequestResolver = authorizationRequestResolver(customClientRegistrationRepository)
            }
        }

        return http.build()
    }

    private fun authorizationRequestResolver(
            clientRegistrationRepository: ReactiveClientRegistrationRepository): ServerOAuth2AuthorizationRequestResolver {
        val authorizationRequestResolver = DefaultServerOAuth2AuthorizationRequestResolver(
                clientRegistrationRepository)
        authorizationRequestResolver.setAuthorizationRequestCustomizer(
                authorizationRequestCustomizer())
        return authorizationRequestResolver
    }

    private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationRequest.Builder> {
        return Consumer { customizer ->
            customizer
                .additionalParameters { params -> params["prompt"] = "consent" }
        }
    }
}

For the simple use case, where the additional request parameter is always the same for a specific provider, it may be added directly in the authorization-uri property.

For example, if the value for the request parameter prompt is always consent for the provider okta, than simply configure as follows:

spring:
  security:
    oauth2:
      client:
        provider:
          okta:
            authorization-uri: https://dev-1234.oktapreview.com/oauth2/v1/authorize?prompt=consent

The preceding example shows the common use case of adding a custom parameter on top of the standard parameters. Alternatively, if your requirements are more advanced, you can take full control in building the Authorization Request URI by simply overriding the OAuth2AuthorizationRequest.authorizationRequestUri property.

OAuth2AuthorizationRequest.Builder.build() constructs the OAuth2AuthorizationRequest.authorizationRequestUri, which represents the Authorization Request URI including all query parameters using the application/x-www-form-urlencoded format.

The following example shows a variation of authorizationRequestCustomizer() from the preceding example, and instead overrides the OAuth2AuthorizationRequest.authorizationRequestUri property.

  • Java

  • Kotlin

private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
	return customizer -> customizer
			.authorizationRequestUri(uriBuilder -> uriBuilder
					.queryParam("prompt", "consent").build());
}
private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationRequest.Builder> {
    return Consumer { customizer: OAuth2AuthorizationRequest.Builder ->
        customizer
                .authorizationRequestUri { uriBuilder: UriBuilder ->
                    uriBuilder
                            .queryParam("prompt", "consent").build()
                }
    }
}

Storing the Authorization Request

The ServerAuthorizationRequestRepository is responsible for the persistence of the OAuth2AuthorizationRequest from the time the Authorization Request is initiated to the time the Authorization Response is received (the callback).

The OAuth2AuthorizationRequest is used to correlate and validate the Authorization Response.

The default implementation of ServerAuthorizationRequestRepository is WebSessionOAuth2ServerAuthorizationRequestRepository, which stores the OAuth2AuthorizationRequest in the WebSession.

If you have a custom implementation of ServerAuthorizationRequestRepository, you may configure it as shown in the following example:

ServerAuthorizationRequestRepository Configuration
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class OAuth2ClientSecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			.oauth2Client(oauth2 -> oauth2
				.authorizationRequestRepository(this.authorizationRequestRepository())
				// ...
			);
		return http.build();
	}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2ClientSecurityConfig {

    @Bean
    fun securityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        http {
            oauth2Client {
                authorizationRequestRepository = authorizationRequestRepository()
            }
        }

        return http.build()
    }
}

Requesting an Access Token

Please refer to the Access Token Request/Response protocol flow for the Authorization Code grant.

The default implementation of ReactiveOAuth2AccessTokenResponseClient for the Authorization Code grant is WebClientReactiveAuthorizationCodeTokenResponseClient, which uses a WebClient for exchanging an authorization code for an access token at the Authorization Server’s Token Endpoint.

To customize WebClientReactiveAuthorizationCodeTokenResponseClient, simply provide a bean as in the following example and it will be picked up by the default ReactiveOAuth2AuthorizedClientManager automatically:

Access Token Response Configuration
  • Java

  • Kotlin

@Bean
public ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
	WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
		new WebClientReactiveAuthorizationCodeTokenResponseClient();
	// ...
	return accessTokenResponseClient;
}
@Bean
fun accessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<Authorization Code> {
	val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
	// ...
	return accessTokenResponseClient
}

WebClientReactiveAuthorizationCodeTokenResponseClient is very flexible and provides several options for customizing the OAuth 2.0 Access Token request and response for the Authorization Code grant. Choose from the following use cases to learn more:

Customizing the Access Token Request

WebClientReactiveAuthorizationCodeTokenResponseClient provides hooks for customizing HTTP headers and request parameters of the Token Request.

Customizing Request Headers

There are two options for customizing HTTP headers:

  • Add additional headers by calling addHeadersConverter()

  • Fully customize headers by calling setHeadersConverter()

You can include additional headers without affecting the default headers added to every request using addHeadersConverter(). The following example adds a User-Agent header to the request when the registrationId is spring:

Include Additional HTTP Headers
  • Java

  • Kotlin

WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.addHeadersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	HttpHeaders headers = new HttpHeaders();
	if (clientRegistration.getRegistrationId().equals("spring")) {
		headers.set(HttpHeaders.USER_AGENT, "my-user-agent");
	}
	return headers;
});
val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.addHeadersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val headers = HttpHeaders()
	if (clientRegistration.getRegistrationId() == "spring") {
        headers[HttpHeaders.USER_AGENT] = "my-user-agent"
	}
	headers
}

You can fully customize headers by re-using DefaultOAuth2TokenRequestHeadersConverter or providing a custom implementation using setHeadersConverter(). The following example re-uses DefaultOAuth2TokenRequestHeadersConverter and disables encodeClientCredentials so that HTTP Basic credentials are no longer encoded with application/x-www-form-urlencoded:

Customize HTTP Headers
  • Java

  • Kotlin

DefaultOAuth2TokenRequestHeadersConverter headersConverter =
	new DefaultOAuth2TokenRequestHeadersConverter();
headersConverter.setEncodeClientCredentials(false);

WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setHeadersConverter(headersConverter);
val headersConverter = DefaultOAuth2TokenRequestHeadersConverter()
headersConverter.setEncodeClientCredentials(false)

val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.setHeadersConverter(headersConverter)

Customizing Request Parameters

There are three options for customizing request parameters:

  • Add additional parameters by calling addParametersConverter()

  • Override parameters by calling setParametersConverter()

  • Fully customize parameters by calling setParametersCustomizer()

Using setParametersConverter() does not fully customize parameters because it would require the user to provide all default parameters themselves. Default parameters are always provided, but can be fully customized or omitted by calling setParametersCustomizer().

You can include additional parameters without affecting the default parameters added to every request using addParametersConverter(). The following example adds an audience parameter to the request when the registrationId is keycloak:

Include Additional Request Parameters
  • Java

  • Kotlin

WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.addParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
	if (clientRegistration.getRegistrationId().equals("keycloak")) {
		parameters.set(OAuth2ParameterNames.AUDIENCE, "my-audience");
	}
	return parameters;
});
val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.addParametersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "keycloak") {
        parameters[OAuth2ParameterNames.AUDIENCE] = "my-audience"
	}
	parameters
}

You can override default parameters using setParametersConverter(). The following example overrides the client_id parameter when the registrationId is okta:

Override Request Parameters
  • Java

  • Kotlin

WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	LinkedMultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
	if (clientRegistration.getRegistrationId().equals("okta")) {
		parameters.set(OAuth2ParameterNames.CLIENT_ID, "my-client");
	}
	return parameters;
});
val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.setParametersConverter { grantRequest ->
    val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "okta") {
        parameters[OAuth2ParameterNames.CLIENT_ID] = "my-client"
	}
	parameters
}

You can fully customize parameters (including omitting default parameters) using setParametersCustomizer(). The following example omits the client_id parameter when the client_assertion parameter is present in the request:

Omit Request Parameters
  • Java

  • Kotlin

WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setParametersCustomizer(parameters -> {
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID);
	}
});
val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

Customizing the Access Token Response

WebClientReactiveAuthorizationCodeTokenResponseClient provides hooks for customizing the OAuth 2.0 Access Token Response.

Customizing Response Parameters

You can customize the conversion of Token Response parameters to an OAuth2AccessTokenResponse by calling setBodyExtractor(). The default implementation provided by OAuth2BodyExtractors.oauth2AccessTokenResponse() parses the response and handles errors accordingly.

The following example provides a starting point for customizing the conversion of Token Response parameters to an OAuth2AccessTokenResponse:

Customize Body Extractor
  • Java

  • Kotlin

WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveAuthorizationCodeTokenResponseClient();

BodyExtractor<Mono<Map<String, Object>>, ReactiveHttpInputMessage> bodyExtractor =
	BodyExtractors.toMono(new ParameterizedTypeReference<>() {});
accessTokenResponseClient.setBodyExtractor((inputMessage, context) ->
	bodyExtractor.extract(inputMessage, context)
		.map(parameters -> OAuth2AccessTokenResponse.withToken("custom-token")
			// ...
			.build()
		)
);
val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()

val bodyExtractor = BodyExtractors.toMono(object : ParameterizedTypeReference<Map<String, Any>>() {})
accessTokenResponseClient.setBodyExtractor { inputMessage, context ->
	bodyExtractor.extract(inputMessage, context).map { parameters ->
		OAuth2AccessTokenResponse.withToken("custom-token")
			// ...
			.build()
	}
}

When providing a custom BodyExtractor, you are responsible for detecting and converting an OAuth 2.0 Error Response to a Mono.error() with OAuth2Error based on parameters of the response.

Customizing the WebClient

Alternatively, if your requirements are more advanced, you can take full control of the request and/or response by providing a pre-configured WebClient to setWebClient() as the following example shows:

Customize WebClient
  • Java

  • Kotlin

WebClient webClient = WebClient.builder()
	// ...
	.build();

WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setWebClient(webClient);
val webClient = WebClient.builder()
	// ...
	.build()

val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.setWebClient(webClient)

Customize using the DSL

Whether you customize WebClientReactiveAuthorizationCodeTokenResponseClient or provide your own implementation of ReactiveOAuth2AccessTokenResponseClient, you can configure it using the DSL (as an alternative to publishing a bean) as shown in the following example:

Access Token Response Configuration via DSL
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class OAuth2ClientSecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			.oauth2Client(oauth2 -> oauth2
				.authenticationManager(this.authorizationCodeAuthenticationManager())
				// ...
			);
		return http.build();
	}

	private ReactiveAuthenticationManager authorizationCodeAuthenticationManager() {
		WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
				new WebClientReactiveAuthorizationCodeTokenResponseClient();
		// ...

		return new OAuth2AuthorizationCodeReactiveAuthenticationManager(accessTokenResponseClient);
	}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2ClientSecurityConfig {

    @Bean
    fun securityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        http {
            oauth2Client {
                authenticationManager = authorizationCodeAuthenticationManager()
            }
        }

        return http.build()
    }

    private fun authorizationCodeAuthenticationManager(): ReactiveAuthenticationManager {
        val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
        // ...

        return OAuth2AuthorizationCodeReactiveAuthenticationManager(accessTokenResponseClient)
    }
}

Refresh Token

Please refer to the OAuth 2.0 Authorization Framework for further details on the Refresh Token.

Refreshing an Access Token

Please refer to the Access Token Request/Response protocol flow for the Refresh Token grant.

The default implementation of ReactiveOAuth2AccessTokenResponseClient for the Refresh Token grant is WebClientReactiveRefreshTokenTokenResponseClient, which uses a WebClient when refreshing an access token at the Authorization Server’s Token Endpoint.

To customize WebClientReactiveRefreshTokenTokenResponseClient, simply provide a bean as in the following example and it will be picked up by the default ReactiveOAuth2AuthorizedClientManager automatically:

Access Token Response Configuration
  • Java

  • Kotlin

@Bean
public ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient() {
	WebClientReactiveRefreshTokenTokenResponseClient accessTokenResponseClient =
		new WebClientReactiveRefreshTokenTokenResponseClient();
	// ...
	return accessTokenResponseClient;
}
@Bean
fun accessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<Refresh Token> {
	val accessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient()
	// ...
	return accessTokenResponseClient
}

WebClientReactiveRefreshTokenTokenResponseClient is very flexible and provides several options for customizing the OAuth 2.0 Access Token request and response for the Refresh Token grant. Choose from the following use cases to learn more:

Customizing the Access Token Request

WebClientReactiveRefreshTokenTokenResponseClient provides hooks for customizing HTTP headers and request parameters of the Token Request.

Customizing Request Headers

There are two options for customizing HTTP headers:

  • Add additional headers by calling addHeadersConverter()

  • Fully customize headers by calling setHeadersConverter()

You can include additional headers without affecting the default headers added to every request using addHeadersConverter(). The following example adds a User-Agent header to the request when the registrationId is spring:

Include Additional HTTP Headers
  • Java

  • Kotlin

WebClientReactiveRefreshTokenTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveRefreshTokenTokenResponseClient();
accessTokenResponseClient.addHeadersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	HttpHeaders headers = new HttpHeaders();
	if (clientRegistration.getRegistrationId().equals("spring")) {
		headers.set(HttpHeaders.USER_AGENT, "my-user-agent");
	}
	return headers;
});
val accessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient()
accessTokenResponseClient.addHeadersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val headers = HttpHeaders()
	if (clientRegistration.getRegistrationId() == "spring") {
        headers[HttpHeaders.USER_AGENT] = "my-user-agent"
	}
	headers
}

You can fully customize headers by re-using DefaultOAuth2TokenRequestHeadersConverter or providing a custom implementation using setHeadersConverter(). The following example re-uses DefaultOAuth2TokenRequestHeadersConverter and disables encodeClientCredentials so that HTTP Basic credentials are no longer encoded with application/x-www-form-urlencoded:

Customize HTTP Headers
  • Java

  • Kotlin

DefaultOAuth2TokenRequestHeadersConverter headersConverter =
	new DefaultOAuth2TokenRequestHeadersConverter();
headersConverter.setEncodeClientCredentials(false);

WebClientReactiveRefreshTokenTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveRefreshTokenTokenResponseClient();
accessTokenResponseClient.setHeadersConverter(headersConverter);
val headersConverter = DefaultOAuth2TokenRequestHeadersConverter()
headersConverter.setEncodeClientCredentials(false)

val accessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient()
accessTokenResponseClient.setHeadersConverter(headersConverter)

Customizing Request Parameters

There are three options for customizing request parameters:

  • Add additional parameters by calling addParametersConverter()

  • Override parameters by calling setParametersConverter()

  • Fully customize parameters by calling setParametersCustomizer()

Using setParametersConverter() does not fully customize parameters because it would require the user to provide all default parameters themselves. Default parameters are always provided, but can be fully customized or omitted by calling setParametersCustomizer().

You can include additional parameters without affecting the default parameters added to every request using addParametersConverter(). The following example adds an audience parameter to the request when the registrationId is keycloak:

Include Additional Request Parameters
  • Java

  • Kotlin

WebClientReactiveRefreshTokenTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveRefreshTokenTokenResponseClient();
accessTokenResponseClient.addParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
	if (clientRegistration.getRegistrationId().equals("keycloak")) {
		parameters.set(OAuth2ParameterNames.AUDIENCE, "my-audience");
	}
	return parameters;
});
val accessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient()
accessTokenResponseClient.addParametersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "keycloak") {
        parameters[OAuth2ParameterNames.AUDIENCE] = "my-audience"
	}
	parameters
}

You can override default parameters using setParametersConverter(). The following example overrides the client_id parameter when the registrationId is okta:

Override Request Parameters
  • Java

  • Kotlin

WebClientReactiveRefreshTokenTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveRefreshTokenTokenResponseClient();
accessTokenResponseClient.setParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	LinkedMultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
	if (clientRegistration.getRegistrationId().equals("okta")) {
		parameters.set(OAuth2ParameterNames.CLIENT_ID, "my-client");
	}
	return parameters;
});
val accessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient()
accessTokenResponseClient.setParametersConverter { grantRequest ->
    val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "okta") {
        parameters[OAuth2ParameterNames.CLIENT_ID] = "my-client"
	}
	parameters
}

You can fully customize parameters (including omitting default parameters) using setParametersCustomizer(). The following example omits the client_id parameter when the client_assertion parameter is present in the request:

Omit Request Parameters
  • Java

  • Kotlin

WebClientReactiveRefreshTokenTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveRefreshTokenTokenResponseClient();
accessTokenResponseClient.setParametersCustomizer(parameters -> {
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID);
	}
});
val accessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient()
accessTokenResponseClient.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

Customizing the Access Token Response

WebClientReactiveRefreshTokenTokenResponseClient provides hooks for customizing the OAuth 2.0 Access Token Response.

Customizing Response Parameters

You can customize the conversion of Token Response parameters to an OAuth2AccessTokenResponse by calling setBodyExtractor(). The default implementation provided by OAuth2BodyExtractors.oauth2AccessTokenResponse() parses the response and handles errors accordingly.

The following example provides a starting point for customizing the conversion of Token Response parameters to an OAuth2AccessTokenResponse:

Customize Body Extractor
  • Java

  • Kotlin

WebClientReactiveRefreshTokenTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveRefreshTokenTokenResponseClient();

BodyExtractor<Mono<Map<String, Object>>, ReactiveHttpInputMessage> bodyExtractor =
	BodyExtractors.toMono(new ParameterizedTypeReference<>() {});
accessTokenResponseClient.setBodyExtractor((inputMessage, context) ->
	bodyExtractor.extract(inputMessage, context)
		.map(parameters -> OAuth2AccessTokenResponse.withToken("custom-token")
			// ...
			.build()
		)
);
val accessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient()

val bodyExtractor = BodyExtractors.toMono(object : ParameterizedTypeReference<Map<String, Any>>() {})
accessTokenResponseClient.setBodyExtractor { inputMessage, context ->
	bodyExtractor.extract(inputMessage, context).map { parameters ->
		OAuth2AccessTokenResponse.withToken("custom-token")
			// ...
			.build()
	}
}

When providing a custom BodyExtractor, you are responsible for detecting and converting an OAuth 2.0 Error Response to a Mono.error() with OAuth2Error based on parameters of the response.

Customizing the WebClient

Alternatively, if your requirements are more advanced, you can take full control of the request and/or response by providing a pre-configured WebClient to setWebClient() as the following example shows:

Customize WebClient
  • Java

  • Kotlin

WebClient webClient = WebClient.builder()
	// ...
	.build();

WebClientReactiveRefreshTokenTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveRefreshTokenTokenResponseClient();
accessTokenResponseClient.setWebClient(webClient);
val webClient = WebClient.builder()
	// ...
	.build()

val accessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient()
accessTokenResponseClient.setWebClient(webClient)

Customize using the Builder

Whether you customize WebClientReactiveRefreshTokenTokenResponseClient or provide your own implementation of ReactiveOAuth2AccessTokenResponseClient, you can configure it using the ReactiveOAuth2AuthorizedClientProviderBuilder (as an alternative to publishing a bean) as follows:

Access Token Response Configuration via Builder
  • Java

  • Kotlin

// Customize
ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient = ...

ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
		ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
				.authorizationCode()
				.refreshToken(configurer -> configurer.accessTokenResponseClient(refreshTokenTokenResponseClient))
				.build();

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// Customize
val refreshTokenTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> = ...

val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
        .authorizationCode()
        .refreshToken { it.accessTokenResponseClient(refreshTokenTokenResponseClient) }
        .build()

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

ReactiveOAuth2AuthorizedClientProviderBuilder.builder().refreshToken() configures a RefreshTokenReactiveOAuth2AuthorizedClientProvider, which is an implementation of a ReactiveOAuth2AuthorizedClientProvider for the Refresh Token grant.

The OAuth2RefreshToken may optionally be returned in the Access Token Response for the authorization_code and password grant types. If the OAuth2AuthorizedClient.getRefreshToken() is available and the OAuth2AuthorizedClient.getAccessToken() is expired, it will automatically be refreshed by the RefreshTokenReactiveOAuth2AuthorizedClientProvider.

Client Credentials

Please refer to the OAuth 2.0 Authorization Framework for further details on the Client Credentials grant.

Requesting an Access Token

Please refer to the Access Token Request/Response protocol flow for the Client Credentials grant.

The default implementation of ReactiveOAuth2AccessTokenResponseClient for the Client Credentials grant is WebClientReactiveClientCredentialsTokenResponseClient, which uses a WebClient when requesting an access token at the Authorization Server’s Token Endpoint.

To customize WebClientReactiveClientCredentialsTokenResponseClient, simply provide a bean as in the following example and it will be picked up by the default ReactiveOAuth2AuthorizedClientManager automatically:

Access Token Response Configuration
  • Java

  • Kotlin

@Bean
public ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient() {
	WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
		new WebClientReactiveClientCredentialsTokenResponseClient();
	// ...
	return accessTokenResponseClient;
}
@Bean
fun accessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<Client Credentials> {
	val accessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
	// ...
	return accessTokenResponseClient
}

WebClientReactiveClientCredentialsTokenResponseClient is very flexible and provides several options for customizing the OAuth 2.0 Access Token request and response for the Client Credentials grant. Choose from the following use cases to learn more:

Customizing the Access Token Request

WebClientReactiveClientCredentialsTokenResponseClient provides hooks for customizing HTTP headers and request parameters of the Token Request.

Customizing Request Headers

There are two options for customizing HTTP headers:

  • Add additional headers by calling addHeadersConverter()

  • Fully customize headers by calling setHeadersConverter()

You can include additional headers without affecting the default headers added to every request using addHeadersConverter(). The following example adds a User-Agent header to the request when the registrationId is spring:

Include Additional HTTP Headers
  • Java

  • Kotlin

WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveClientCredentialsTokenResponseClient();
accessTokenResponseClient.addHeadersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	HttpHeaders headers = new HttpHeaders();
	if (clientRegistration.getRegistrationId().equals("spring")) {
		headers.set(HttpHeaders.USER_AGENT, "my-user-agent");
	}
	return headers;
});
val accessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
accessTokenResponseClient.addHeadersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val headers = HttpHeaders()
	if (clientRegistration.getRegistrationId() == "spring") {
        headers[HttpHeaders.USER_AGENT] = "my-user-agent"
	}
	headers
}

You can fully customize headers by re-using DefaultOAuth2TokenRequestHeadersConverter or providing a custom implementation using setHeadersConverter(). The following example re-uses DefaultOAuth2TokenRequestHeadersConverter and disables encodeClientCredentials so that HTTP Basic credentials are no longer encoded with application/x-www-form-urlencoded:

Customize HTTP Headers
  • Java

  • Kotlin

DefaultOAuth2TokenRequestHeadersConverter headersConverter =
	new DefaultOAuth2TokenRequestHeadersConverter();
headersConverter.setEncodeClientCredentials(false);

WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveClientCredentialsTokenResponseClient();
accessTokenResponseClient.setHeadersConverter(headersConverter);
val headersConverter = DefaultOAuth2TokenRequestHeadersConverter()
headersConverter.setEncodeClientCredentials(false)

val accessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
accessTokenResponseClient.setHeadersConverter(headersConverter)

Customizing Request Parameters

There are three options for customizing request parameters:

  • Add additional parameters by calling addParametersConverter()

  • Override parameters by calling setParametersConverter()

  • Fully customize parameters by calling setParametersCustomizer()

Using setParametersConverter() does not fully customize parameters because it would require the user to provide all default parameters themselves. Default parameters are always provided, but can be fully customized or omitted by calling setParametersCustomizer().

You can include additional parameters without affecting the default parameters added to every request using addParametersConverter(). The following example adds an audience parameter to the request when the registrationId is keycloak:

Include Additional Request Parameters
  • Java

  • Kotlin

WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveClientCredentialsTokenResponseClient();
accessTokenResponseClient.addParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
	if (clientRegistration.getRegistrationId().equals("keycloak")) {
		parameters.set(OAuth2ParameterNames.AUDIENCE, "my-audience");
	}
	return parameters;
});
val accessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
accessTokenResponseClient.addParametersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "keycloak") {
        parameters[OAuth2ParameterNames.AUDIENCE] = "my-audience"
	}
	parameters
}

You can override default parameters using setParametersConverter(). The following example overrides the client_id parameter when the registrationId is okta:

Override Request Parameters
  • Java

  • Kotlin

WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveClientCredentialsTokenResponseClient();
accessTokenResponseClient.setParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	LinkedMultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
	if (clientRegistration.getRegistrationId().equals("okta")) {
		parameters.set(OAuth2ParameterNames.CLIENT_ID, "my-client");
	}
	return parameters;
});
val accessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
accessTokenResponseClient.setParametersConverter { grantRequest ->
    val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "okta") {
        parameters[OAuth2ParameterNames.CLIENT_ID] = "my-client"
	}
	parameters
}

You can fully customize parameters (including omitting default parameters) using setParametersCustomizer(). The following example omits the client_id parameter when the client_assertion parameter is present in the request:

Omit Request Parameters
  • Java

  • Kotlin

WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveClientCredentialsTokenResponseClient();
accessTokenResponseClient.setParametersCustomizer(parameters -> {
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID);
	}
});
val accessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
accessTokenResponseClient.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

Customizing the Access Token Response

WebClientReactiveClientCredentialsTokenResponseClient provides hooks for customizing the OAuth 2.0 Access Token Response.

Customizing Response Parameters

You can customize the conversion of Token Response parameters to an OAuth2AccessTokenResponse by calling setBodyExtractor(). The default implementation provided by OAuth2BodyExtractors.oauth2AccessTokenResponse() parses the response and handles errors accordingly.

The following example provides a starting point for customizing the conversion of Token Response parameters to an OAuth2AccessTokenResponse:

Customize Body Extractor
  • Java

  • Kotlin

WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveClientCredentialsTokenResponseClient();

BodyExtractor<Mono<Map<String, Object>>, ReactiveHttpInputMessage> bodyExtractor =
	BodyExtractors.toMono(new ParameterizedTypeReference<>() {});
accessTokenResponseClient.setBodyExtractor((inputMessage, context) ->
	bodyExtractor.extract(inputMessage, context)
		.map(parameters -> OAuth2AccessTokenResponse.withToken("custom-token")
			// ...
			.build()
		)
);
val accessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()

val bodyExtractor = BodyExtractors.toMono(object : ParameterizedTypeReference<Map<String, Any>>() {})
accessTokenResponseClient.setBodyExtractor { inputMessage, context ->
	bodyExtractor.extract(inputMessage, context).map { parameters ->
		OAuth2AccessTokenResponse.withToken("custom-token")
			// ...
			.build()
	}
}

When providing a custom BodyExtractor, you are responsible for detecting and converting an OAuth 2.0 Error Response to a Mono.error() with OAuth2Error based on parameters of the response.

Customizing the WebClient

Alternatively, if your requirements are more advanced, you can take full control of the request and/or response by providing a pre-configured WebClient to setWebClient() as the following example shows:

Customize WebClient
  • Java

  • Kotlin

WebClient webClient = WebClient.builder()
	// ...
	.build();

WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveClientCredentialsTokenResponseClient();
accessTokenResponseClient.setWebClient(webClient);
val webClient = WebClient.builder()
	// ...
	.build()

val accessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
accessTokenResponseClient.setWebClient(webClient)

Customize using the Builder

Whether you customize WebClientReactiveClientCredentialsTokenResponseClient or provide your own implementation of ReactiveOAuth2AccessTokenResponseClient, you can configure it using the ReactiveOAuth2AuthorizedClientProviderBuilder (as an alternative to publishing a bean) as follows:

Access Token Response Configuration via Builder
  • Java

  • Kotlin

// Customize
ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient = ...

ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
		ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
				.clientCredentials(configurer -> configurer.accessTokenResponseClient(clientCredentialsTokenResponseClient))
				.build();

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// Customize
val clientCredentialsTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> = ...

val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
        .clientCredentials { it.accessTokenResponseClient(clientCredentialsTokenResponseClient) }
        .build()

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

ReactiveOAuth2AuthorizedClientProviderBuilder.builder().clientCredentials() configures a ClientCredentialsReactiveOAuth2AuthorizedClientProvider, which is an implementation of a ReactiveOAuth2AuthorizedClientProvider for the Client Credentials grant.

Using the Access Token

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

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: client_credentials
            scope: read, write
        provider:
          okta:
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

…​and the ReactiveOAuth2AuthorizedClientManager @Bean:

  • Java

  • Kotlin

@Bean
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
		ReactiveClientRegistrationRepository clientRegistrationRepository,
		ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {

	ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
			ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
					.clientCredentials()
					.build();

	DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
			new DefaultReactiveOAuth2AuthorizedClientManager(
					clientRegistrationRepository, authorizedClientRepository);
	authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

	return authorizedClientManager;
}
@Bean
fun authorizedClientManager(
        clientRegistrationRepository: ReactiveClientRegistrationRepository,
        authorizedClientRepository: ServerOAuth2AuthorizedClientRepository): ReactiveOAuth2AuthorizedClientManager {
    val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
            .clientCredentials()
            .build()
    val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientRepository)
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
    return authorizedClientManager
}

You may obtain the OAuth2AccessToken as follows:

  • Java

  • Kotlin

@Controller
public class OAuth2ClientController {

	@Autowired
	private ReactiveOAuth2AuthorizedClientManager authorizedClientManager;

	@GetMapping("/")
	public Mono<String> index(Authentication authentication, ServerWebExchange exchange) {
		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
				.principal(authentication)
				.attribute(ServerWebExchange.class.getName(), exchange)
				.build();

		return this.authorizedClientManager.authorize(authorizeRequest)
				.map(OAuth2AuthorizedClient::getAccessToken)
				// ...
				.thenReturn("index");
	}
}
class OAuth2ClientController {

    @Autowired
    private lateinit var authorizedClientManager: ReactiveOAuth2AuthorizedClientManager

    @GetMapping("/")
    fun index(authentication: Authentication, exchange: ServerWebExchange): Mono<String> {
        val authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
                .principal(authentication)
                .attribute(ServerWebExchange::class.java.name, exchange)
                .build()

        return authorizedClientManager.authorize(authorizeRequest)
                .map { it.accessToken }
                // ...
                .thenReturn("index")
    }
}

ServerWebExchange is an OPTIONAL attribute. If not provided, it will be obtained from the Reactor’s Context via the key ServerWebExchange.class.

Resource Owner Password Credentials

Please refer to the OAuth 2.0 Authorization Framework for further details on the Resource Owner Password Credentials grant.

Requesting an Access Token

Please refer to the Access Token Request/Response protocol flow for the Resource Owner Password Credentials grant.

The default implementation of ReactiveOAuth2AccessTokenResponseClient for the Resource Owner Password Credentials grant is WebClientReactivePasswordTokenResponseClient, which uses a WebClient when requesting an access token at the Authorization Server’s Token Endpoint.

The WebClientReactivePasswordTokenResponseClient class and support for the Resource Owner Password Credentials grant are deprecated. This section will be removed in Spring Security 7.

To customize WebClientReactivePasswordTokenResponseClient, simply provide a bean as in the following example and it will be picked up by the default ReactiveOAuth2AuthorizedClientManager automatically:

Access Token Response Configuration
  • Java

  • Kotlin

@Bean
public ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient() {
	WebClientReactivePasswordTokenResponseClient accessTokenResponseClient =
		new WebClientReactivePasswordTokenResponseClient();
	// ...
	return accessTokenResponseClient;
}
@Bean
fun accessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<Password> {
	val accessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()
	// ...
	return accessTokenResponseClient
}

WebClientReactivePasswordTokenResponseClient is very flexible and provides several options for customizing the OAuth 2.0 Access Token request and response for the Password grant. Choose from the following use cases to learn more:

Customizing the Access Token Request

WebClientReactivePasswordTokenResponseClient provides hooks for customizing HTTP headers and request parameters of the Token Request.

Customizing Request Headers

There are two options for customizing HTTP headers:

  • Add additional headers by calling addHeadersConverter()

  • Fully customize headers by calling setHeadersConverter()

You can include additional headers without affecting the default headers added to every request using addHeadersConverter(). The following example adds a User-Agent header to the request when the registrationId is spring:

Include Additional HTTP Headers
  • Java

  • Kotlin

WebClientReactivePasswordTokenResponseClient accessTokenResponseClient =
	new WebClientReactivePasswordTokenResponseClient();
accessTokenResponseClient.addHeadersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	HttpHeaders headers = new HttpHeaders();
	if (clientRegistration.getRegistrationId().equals("spring")) {
		headers.set(HttpHeaders.USER_AGENT, "my-user-agent");
	}
	return headers;
});
val accessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()
accessTokenResponseClient.addHeadersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val headers = HttpHeaders()
	if (clientRegistration.getRegistrationId() == "spring") {
        headers[HttpHeaders.USER_AGENT] = "my-user-agent"
	}
	headers
}

You can fully customize headers by re-using DefaultOAuth2TokenRequestHeadersConverter or providing a custom implementation using setHeadersConverter(). The following example re-uses DefaultOAuth2TokenRequestHeadersConverter and disables encodeClientCredentials so that HTTP Basic credentials are no longer encoded with application/x-www-form-urlencoded:

Customize HTTP Headers
  • Java

  • Kotlin

DefaultOAuth2TokenRequestHeadersConverter headersConverter =
	new DefaultOAuth2TokenRequestHeadersConverter();
headersConverter.setEncodeClientCredentials(false);

WebClientReactivePasswordTokenResponseClient accessTokenResponseClient =
	new WebClientReactivePasswordTokenResponseClient();
accessTokenResponseClient.setHeadersConverter(headersConverter);
val headersConverter = DefaultOAuth2TokenRequestHeadersConverter()
headersConverter.setEncodeClientCredentials(false)

val accessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()
accessTokenResponseClient.setHeadersConverter(headersConverter)

Customizing Request Parameters

There are three options for customizing request parameters:

  • Add additional parameters by calling addParametersConverter()

  • Override parameters by calling setParametersConverter()

  • Fully customize parameters by calling setParametersCustomizer()

Using setParametersConverter() does not fully customize parameters because it would require the user to provide all default parameters themselves. Default parameters are always provided, but can be fully customized or omitted by calling setParametersCustomizer().

You can include additional parameters without affecting the default parameters added to every request using addParametersConverter(). The following example adds an audience parameter to the request when the registrationId is keycloak:

Include Additional Request Parameters
  • Java

  • Kotlin

WebClientReactivePasswordTokenResponseClient accessTokenResponseClient =
	new WebClientReactivePasswordTokenResponseClient();
accessTokenResponseClient.addParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
	if (clientRegistration.getRegistrationId().equals("keycloak")) {
		parameters.set(OAuth2ParameterNames.AUDIENCE, "my-audience");
	}
	return parameters;
});
val accessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()
accessTokenResponseClient.addParametersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "keycloak") {
        parameters[OAuth2ParameterNames.AUDIENCE] = "my-audience"
	}
	parameters
}

You can override default parameters using setParametersConverter(). The following example overrides the client_id parameter when the registrationId is okta:

Override Request Parameters
  • Java

  • Kotlin

WebClientReactivePasswordTokenResponseClient accessTokenResponseClient =
	new WebClientReactivePasswordTokenResponseClient();
accessTokenResponseClient.setParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	LinkedMultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
	if (clientRegistration.getRegistrationId().equals("okta")) {
		parameters.set(OAuth2ParameterNames.CLIENT_ID, "my-client");
	}
	return parameters;
});
val accessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()
accessTokenResponseClient.setParametersConverter { grantRequest ->
    val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "okta") {
        parameters[OAuth2ParameterNames.CLIENT_ID] = "my-client"
	}
	parameters
}

You can fully customize parameters (including omitting default parameters) using setParametersCustomizer(). The following example omits the client_id parameter when the client_assertion parameter is present in the request:

Omit Request Parameters
  • Java

  • Kotlin

WebClientReactivePasswordTokenResponseClient accessTokenResponseClient =
	new WebClientReactivePasswordTokenResponseClient();
accessTokenResponseClient.setParametersCustomizer(parameters -> {
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID);
	}
});
val accessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()
accessTokenResponseClient.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

Customizing the Access Token Response

WebClientReactivePasswordTokenResponseClient provides hooks for customizing the OAuth 2.0 Access Token Response.

Customizing Response Parameters

You can customize the conversion of Token Response parameters to an OAuth2AccessTokenResponse by calling setBodyExtractor(). The default implementation provided by OAuth2BodyExtractors.oauth2AccessTokenResponse() parses the response and handles errors accordingly.

The following example provides a starting point for customizing the conversion of Token Response parameters to an OAuth2AccessTokenResponse:

Customize Body Extractor
  • Java

  • Kotlin

WebClientReactivePasswordTokenResponseClient accessTokenResponseClient =
	new WebClientReactivePasswordTokenResponseClient();

BodyExtractor<Mono<Map<String, Object>>, ReactiveHttpInputMessage> bodyExtractor =
	BodyExtractors.toMono(new ParameterizedTypeReference<>() {});
accessTokenResponseClient.setBodyExtractor((inputMessage, context) ->
	bodyExtractor.extract(inputMessage, context)
		.map(parameters -> OAuth2AccessTokenResponse.withToken("custom-token")
			// ...
			.build()
		)
);
val accessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()

val bodyExtractor = BodyExtractors.toMono(object : ParameterizedTypeReference<Map<String, Any>>() {})
accessTokenResponseClient.setBodyExtractor { inputMessage, context ->
	bodyExtractor.extract(inputMessage, context).map { parameters ->
		OAuth2AccessTokenResponse.withToken("custom-token")
			// ...
			.build()
	}
}

When providing a custom BodyExtractor, you are responsible for detecting and converting an OAuth 2.0 Error Response to a Mono.error() with OAuth2Error based on parameters of the response.

Customizing the WebClient

Alternatively, if your requirements are more advanced, you can take full control of the request and/or response by providing a pre-configured WebClient to setWebClient() as the following example shows:

Customize WebClient
  • Java

  • Kotlin

WebClient webClient = WebClient.builder()
	// ...
	.build();

WebClientReactivePasswordTokenResponseClient accessTokenResponseClient =
	new WebClientReactivePasswordTokenResponseClient();
accessTokenResponseClient.setWebClient(webClient);
val webClient = WebClient.builder()
	// ...
	.build()

val accessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()
accessTokenResponseClient.setWebClient(webClient)

Customize using the Builder

Whether you customize WebClientReactivePasswordTokenResponseClient or provide your own implementation of ReactiveOAuth2AccessTokenResponseClient, you can configure it using the ReactiveOAuth2AuthorizedClientProviderBuilder (as an alternative to publishing a bean) as follows:

Access Token Response Configuration via Builder
  • Java

  • Kotlin

// Customize
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient = ...

ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
		ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
				.password(configurer -> configurer.accessTokenResponseClient(passwordTokenResponseClient))
				.refreshToken()
				.build();

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
val passwordTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> = ...

val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
        .password { it.accessTokenResponseClient(passwordTokenResponseClient) }
        .refreshToken()
        .build()

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

ReactiveOAuth2AuthorizedClientProviderBuilder.builder().password() configures a PasswordReactiveOAuth2AuthorizedClientProvider, which is an implementation of a ReactiveOAuth2AuthorizedClientProvider for the Resource Owner Password Credentials grant.

Using the Access Token

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

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: password
            scope: read, write
        provider:
          okta:
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

…​and the ReactiveOAuth2AuthorizedClientManager @Bean:

  • Java

  • Kotlin

@Bean
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
		ReactiveClientRegistrationRepository clientRegistrationRepository,
		ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {

	ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
			ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
					.password()
					.refreshToken()
					.build();

	DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
			new DefaultReactiveOAuth2AuthorizedClientManager(
					clientRegistrationRepository, authorizedClientRepository);
	authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

	// Assuming the `username` and `password` are supplied as `ServerHttpRequest` parameters,
	// map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
	authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());

	return authorizedClientManager;
}

private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper() {
	return authorizeRequest -> {
		Map<String, Object> contextAttributes = Collections.emptyMap();
		ServerWebExchange exchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName());
		ServerHttpRequest request = exchange.getRequest();
		String username = request.getQueryParams().getFirst(OAuth2ParameterNames.USERNAME);
		String password = request.getQueryParams().getFirst(OAuth2ParameterNames.PASSWORD);
		if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
			contextAttributes = new HashMap<>();

			// `PasswordReactiveOAuth2AuthorizedClientProvider` requires both attributes
			contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
			contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
		}
		return Mono.just(contextAttributes);
	};
}
@Bean
fun authorizedClientManager(
        clientRegistrationRepository: ReactiveClientRegistrationRepository,
        authorizedClientRepository: ServerOAuth2AuthorizedClientRepository): ReactiveOAuth2AuthorizedClientManager {
    val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
            .password()
            .refreshToken()
            .build()
    val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientRepository)
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

    // Assuming the `username` and `password` are supplied as `ServerHttpRequest` parameters,
    // map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
    authorizedClientManager.setContextAttributesMapper(contextAttributesMapper())
    return authorizedClientManager
}

private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, Mono<MutableMap<String, Any>>> {
    return Function { authorizeRequest ->
        var contextAttributes: MutableMap<String, Any> = mutableMapOf()
        val exchange: ServerWebExchange = authorizeRequest.getAttribute(ServerWebExchange::class.java.name)!!
        val request: ServerHttpRequest = exchange.request
        val username: String? = request.queryParams.getFirst(OAuth2ParameterNames.USERNAME)
        val password: String? = request.queryParams.getFirst(OAuth2ParameterNames.PASSWORD)
        if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
            contextAttributes = hashMapOf()

            // `PasswordReactiveOAuth2AuthorizedClientProvider` requires both attributes
            contextAttributes[OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME] = username!!
            contextAttributes[OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME] = password!!
        }
        Mono.just(contextAttributes)
    }
}

You may obtain the OAuth2AccessToken as follows:

  • Java

  • Kotlin

@Controller
public class OAuth2ClientController {

	@Autowired
	private ReactiveOAuth2AuthorizedClientManager authorizedClientManager;

	@GetMapping("/")
	public Mono<String> index(Authentication authentication, ServerWebExchange exchange) {
		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
				.principal(authentication)
				.attribute(ServerWebExchange.class.getName(), exchange)
				.build();

		return this.authorizedClientManager.authorize(authorizeRequest)
				.map(OAuth2AuthorizedClient::getAccessToken)
				// ...
				.thenReturn("index");
	}
}
@Controller
class OAuth2ClientController {
    @Autowired
    private lateinit var authorizedClientManager: ReactiveOAuth2AuthorizedClientManager

    @GetMapping("/")
    fun index(authentication: Authentication, exchange: ServerWebExchange): Mono<String> {
        val authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
                .principal(authentication)
                .attribute(ServerWebExchange::class.java.name, exchange)
                .build()

        return authorizedClientManager.authorize(authorizeRequest)
                .map { it.accessToken }
                // ...
                .thenReturn("index")
    }
}

ServerWebExchange is an OPTIONAL attribute. If not provided, it will be obtained from the Reactor’s Context via the key ServerWebExchange.class.

JWT Bearer

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

Requesting an Access Token

Please refer to the Access Token Request/Response protocol flow for the JWT Bearer grant.

The default implementation of ReactiveOAuth2AccessTokenResponseClient for the JWT Bearer grant is WebClientReactiveJwtBearerTokenResponseClient, which uses a WebClient when requesting an access token at the Authorization Server’s Token Endpoint.

To customize WebClientReactiveJwtBearerTokenResponseClient, simply provide a bean as in the following example and it will be picked up by the default ReactiveOAuth2AuthorizedClientManager automatically:

Access Token Response Configuration
  • Java

  • Kotlin

@Bean
public ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> accessTokenResponseClient() {
	WebClientReactiveJwtBearerTokenResponseClient accessTokenResponseClient =
		new WebClientReactiveJwtBearerTokenResponseClient();
	// ...
	return accessTokenResponseClient;
}
@Bean
fun accessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<JWT Bearer> {
	val accessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
	// ...
	return accessTokenResponseClient
}

WebClientReactiveJwtBearerTokenResponseClient is very flexible and provides several options for customizing the OAuth 2.0 Access Token request and response for the JWT Bearer grant. Choose from the following use cases to learn more:

Customizing the Access Token Request

WebClientReactiveJwtBearerTokenResponseClient provides hooks for customizing HTTP headers and request parameters of the Token Request.

Customizing Request Headers

There are two options for customizing HTTP headers:

  • Add additional headers by calling addHeadersConverter()

  • Fully customize headers by calling setHeadersConverter()

You can include additional headers without affecting the default headers added to every request using addHeadersConverter(). The following example adds a User-Agent header to the request when the registrationId is spring:

Include Additional HTTP Headers
  • Java

  • Kotlin

WebClientReactiveJwtBearerTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveJwtBearerTokenResponseClient();
accessTokenResponseClient.addHeadersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	HttpHeaders headers = new HttpHeaders();
	if (clientRegistration.getRegistrationId().equals("spring")) {
		headers.set(HttpHeaders.USER_AGENT, "my-user-agent");
	}
	return headers;
});
val accessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
accessTokenResponseClient.addHeadersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val headers = HttpHeaders()
	if (clientRegistration.getRegistrationId() == "spring") {
        headers[HttpHeaders.USER_AGENT] = "my-user-agent"
	}
	headers
}

You can fully customize headers by re-using DefaultOAuth2TokenRequestHeadersConverter or providing a custom implementation using setHeadersConverter(). The following example re-uses DefaultOAuth2TokenRequestHeadersConverter and disables encodeClientCredentials so that HTTP Basic credentials are no longer encoded with application/x-www-form-urlencoded:

Customize HTTP Headers
  • Java

  • Kotlin

DefaultOAuth2TokenRequestHeadersConverter headersConverter =
	new DefaultOAuth2TokenRequestHeadersConverter();
headersConverter.setEncodeClientCredentials(false);

WebClientReactiveJwtBearerTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveJwtBearerTokenResponseClient();
accessTokenResponseClient.setHeadersConverter(headersConverter);
val headersConverter = DefaultOAuth2TokenRequestHeadersConverter()
headersConverter.setEncodeClientCredentials(false)

val accessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
accessTokenResponseClient.setHeadersConverter(headersConverter)

Customizing Request Parameters

There are three options for customizing request parameters:

  • Add additional parameters by calling addParametersConverter()

  • Override parameters by calling setParametersConverter()

  • Fully customize parameters by calling setParametersCustomizer()

Using setParametersConverter() does not fully customize parameters because it would require the user to provide all default parameters themselves. Default parameters are always provided, but can be fully customized or omitted by calling setParametersCustomizer().

You can include additional parameters without affecting the default parameters added to every request using addParametersConverter(). The following example adds an audience parameter to the request when the registrationId is keycloak:

Include Additional Request Parameters
  • Java

  • Kotlin

WebClientReactiveJwtBearerTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveJwtBearerTokenResponseClient();
accessTokenResponseClient.addParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
	if (clientRegistration.getRegistrationId().equals("keycloak")) {
		parameters.set(OAuth2ParameterNames.AUDIENCE, "my-audience");
	}
	return parameters;
});
val accessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
accessTokenResponseClient.addParametersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "keycloak") {
        parameters[OAuth2ParameterNames.AUDIENCE] = "my-audience"
	}
	parameters
}

You can override default parameters using setParametersConverter(). The following example overrides the client_id parameter when the registrationId is okta:

Override Request Parameters
  • Java

  • Kotlin

WebClientReactiveJwtBearerTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveJwtBearerTokenResponseClient();
accessTokenResponseClient.setParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	LinkedMultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
	if (clientRegistration.getRegistrationId().equals("okta")) {
		parameters.set(OAuth2ParameterNames.CLIENT_ID, "my-client");
	}
	return parameters;
});
val accessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
accessTokenResponseClient.setParametersConverter { grantRequest ->
    val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "okta") {
        parameters[OAuth2ParameterNames.CLIENT_ID] = "my-client"
	}
	parameters
}

You can fully customize parameters (including omitting default parameters) using setParametersCustomizer(). The following example omits the client_id parameter when the client_assertion parameter is present in the request:

Omit Request Parameters
  • Java

  • Kotlin

WebClientReactiveJwtBearerTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveJwtBearerTokenResponseClient();
accessTokenResponseClient.setParametersCustomizer(parameters -> {
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID);
	}
});
val accessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
accessTokenResponseClient.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

Customizing the Access Token Response

WebClientReactiveJwtBearerTokenResponseClient provides hooks for customizing the OAuth 2.0 Access Token Response.

Customizing Response Parameters

You can customize the conversion of Token Response parameters to an OAuth2AccessTokenResponse by calling setBodyExtractor(). The default implementation provided by OAuth2BodyExtractors.oauth2AccessTokenResponse() parses the response and handles errors accordingly.

The following example provides a starting point for customizing the conversion of Token Response parameters to an OAuth2AccessTokenResponse:

Customize Body Extractor
  • Java

  • Kotlin

WebClientReactiveJwtBearerTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveJwtBearerTokenResponseClient();

BodyExtractor<Mono<Map<String, Object>>, ReactiveHttpInputMessage> bodyExtractor =
	BodyExtractors.toMono(new ParameterizedTypeReference<>() {});
accessTokenResponseClient.setBodyExtractor((inputMessage, context) ->
	bodyExtractor.extract(inputMessage, context)
		.map(parameters -> OAuth2AccessTokenResponse.withToken("custom-token")
			// ...
			.build()
		)
);
val accessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()

val bodyExtractor = BodyExtractors.toMono(object : ParameterizedTypeReference<Map<String, Any>>() {})
accessTokenResponseClient.setBodyExtractor { inputMessage, context ->
	bodyExtractor.extract(inputMessage, context).map { parameters ->
		OAuth2AccessTokenResponse.withToken("custom-token")
			// ...
			.build()
	}
}

When providing a custom BodyExtractor, you are responsible for detecting and converting an OAuth 2.0 Error Response to a Mono.error() with OAuth2Error based on parameters of the response.

Customizing the WebClient

Alternatively, if your requirements are more advanced, you can take full control of the request and/or response by providing a pre-configured WebClient to setWebClient() as the following example shows:

Customize WebClient
  • Java

  • Kotlin

WebClient webClient = WebClient.builder()
	// ...
	.build();

WebClientReactiveJwtBearerTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveJwtBearerTokenResponseClient();
accessTokenResponseClient.setWebClient(webClient);
val webClient = WebClient.builder()
	// ...
	.build()

val accessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
accessTokenResponseClient.setWebClient(webClient)

Customize using the Builder

Whether you customize WebClientReactiveJwtBearerTokenResponseClient or provide your own implementation of ReactiveOAuth2AccessTokenResponseClient, you can configure it using the ReactiveOAuth2AuthorizedClientProviderBuilder (as an alternative to publishing a bean) as follows:

Access Token Response Configuration via Builder
  • Java

  • Kotlin

// Customize
ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerTokenResponseClient = ...

JwtBearerReactiveOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = new JwtBearerReactiveOAuth2AuthorizedClientProvider();
jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerTokenResponseClient);

ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
		ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
				.provider(jwtBearerAuthorizedClientProvider)
				.build();

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// Customize
val jwtBearerTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> = ...

val jwtBearerAuthorizedClientProvider = JwtBearerReactiveOAuth2AuthorizedClientProvider()
jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerTokenResponseClient)

val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
        .provider(jwtBearerAuthorizedClientProvider)
        .build()

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

Using the Access Token

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

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: urn:ietf:params:oauth:grant-type:jwt-bearer
            scope: read
        provider:
          okta:
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

…​and the OAuth2AuthorizedClientManager @Bean:

  • Java

  • Kotlin

@Bean
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
		ReactiveClientRegistrationRepository clientRegistrationRepository,
		ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {

	JwtBearerReactiveOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider =
			new JwtBearerReactiveOAuth2AuthorizedClientProvider();

	ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
			ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
					.provider(jwtBearerAuthorizedClientProvider)
					.build();

	DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
			new DefaultReactiveOAuth2AuthorizedClientManager(
					clientRegistrationRepository, authorizedClientRepository);
	authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

	return authorizedClientManager;
}
@Bean
fun authorizedClientManager(
        clientRegistrationRepository: ReactiveClientRegistrationRepository,
        authorizedClientRepository: ServerOAuth2AuthorizedClientRepository): ReactiveOAuth2AuthorizedClientManager {
    val jwtBearerAuthorizedClientProvider = JwtBearerReactiveOAuth2AuthorizedClientProvider()
    val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
            .provider(jwtBearerAuthorizedClientProvider)
            .build()
    val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientRepository)
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
    return authorizedClientManager
}

You may obtain the OAuth2AccessToken as follows:

  • Java

  • Kotlin

@RestController
public class OAuth2ResourceServerController {

	@Autowired
	private ReactiveOAuth2AuthorizedClientManager authorizedClientManager;

	@GetMapping("/resource")
	public Mono<String> resource(JwtAuthenticationToken jwtAuthentication, ServerWebExchange exchange) {
		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
				.principal(jwtAuthentication)
				.build();

		return this.authorizedClientManager.authorize(authorizeRequest)
				.map(OAuth2AuthorizedClient::getAccessToken)
				// ...
				.thenReturn("index");
	}
}
class OAuth2ResourceServerController {

    @Autowired
    private lateinit var authorizedClientManager: ReactiveOAuth2AuthorizedClientManager

    @GetMapping("/resource")
    fun resource(jwtAuthentication: JwtAuthenticationToken, exchange: ServerWebExchange): Mono<String> {
        val authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
                .principal(jwtAuthentication)
                .build()
        return authorizedClientManager.authorize(authorizeRequest)
                .map { it.accessToken }
                // ...
                .thenReturn("index")
    }
}

JwtBearerReactiveOAuth2AuthorizedClientProvider resolves the Jwt assertion via OAuth2AuthorizationContext.getPrincipal().getPrincipal() by default, hence the use of JwtAuthenticationToken in the preceding example.

If you need to resolve the Jwt assertion from a different source, you can provide JwtBearerReactiveOAuth2AuthorizedClientProvider.setJwtAssertionResolver() with a custom Function<OAuth2AuthorizationContext, Mono<Jwt>>.

Token Exchange

Please refer to OAuth 2.0 Token Exchange for further details on the Token Exchange grant.

Requesting an Access Token

Please refer to the Token Exchange Request and Response protocol flow for the Token Exchange grant.

The default implementation of ReactiveOAuth2AccessTokenResponseClient for the Token Exchange grant is WebClientReactiveTokenExchangeTokenResponseClient, which uses a WebClient when requesting an access token at the Authorization Server’s Token Endpoint.

To customize WebClientReactiveTokenExchangeTokenResponseClient, simply provide a bean as in the following example and it will be picked up by the default ReactiveOAuth2AuthorizedClientManager automatically:

Access Token Response Configuration
  • Java

  • Kotlin

@Bean
public ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> accessTokenResponseClient() {
	WebClientReactiveTokenExchangeTokenResponseClient accessTokenResponseClient =
		new WebClientReactiveTokenExchangeTokenResponseClient();
	// ...
	return accessTokenResponseClient;
}
@Bean
fun accessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<Token Exchange> {
	val accessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient()
	// ...
	return accessTokenResponseClient
}

WebClientReactiveTokenExchangeTokenResponseClient is very flexible and provides several options for customizing the OAuth 2.0 Access Token request and response for the Token Exchange grant. Choose from the following use cases to learn more:

Customizing the Access Token Request

WebClientReactiveTokenExchangeTokenResponseClient provides hooks for customizing HTTP headers and request parameters of the Token Request.

Customizing Request Headers

There are two options for customizing HTTP headers:

  • Add additional headers by calling addHeadersConverter()

  • Fully customize headers by calling setHeadersConverter()

You can include additional headers without affecting the default headers added to every request using addHeadersConverter(). The following example adds a User-Agent header to the request when the registrationId is spring:

Include Additional HTTP Headers
  • Java

  • Kotlin

WebClientReactiveTokenExchangeTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveTokenExchangeTokenResponseClient();
accessTokenResponseClient.addHeadersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	HttpHeaders headers = new HttpHeaders();
	if (clientRegistration.getRegistrationId().equals("spring")) {
		headers.set(HttpHeaders.USER_AGENT, "my-user-agent");
	}
	return headers;
});
val accessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient()
accessTokenResponseClient.addHeadersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val headers = HttpHeaders()
	if (clientRegistration.getRegistrationId() == "spring") {
        headers[HttpHeaders.USER_AGENT] = "my-user-agent"
	}
	headers
}

You can fully customize headers by re-using DefaultOAuth2TokenRequestHeadersConverter or providing a custom implementation using setHeadersConverter(). The following example re-uses DefaultOAuth2TokenRequestHeadersConverter and disables encodeClientCredentials so that HTTP Basic credentials are no longer encoded with application/x-www-form-urlencoded:

Customize HTTP Headers
  • Java

  • Kotlin

DefaultOAuth2TokenRequestHeadersConverter headersConverter =
	new DefaultOAuth2TokenRequestHeadersConverter();
headersConverter.setEncodeClientCredentials(false);

WebClientReactiveTokenExchangeTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveTokenExchangeTokenResponseClient();
accessTokenResponseClient.setHeadersConverter(headersConverter);
val headersConverter = DefaultOAuth2TokenRequestHeadersConverter()
headersConverter.setEncodeClientCredentials(false)

val accessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient()
accessTokenResponseClient.setHeadersConverter(headersConverter)

Customizing Request Parameters

There are three options for customizing request parameters:

  • Add additional parameters by calling addParametersConverter()

  • Override parameters by calling setParametersConverter()

  • Fully customize parameters by calling setParametersCustomizer()

Using setParametersConverter() does not fully customize parameters because it would require the user to provide all default parameters themselves. Default parameters are always provided, but can be fully customized or omitted by calling setParametersCustomizer().

You can include additional parameters without affecting the default parameters added to every request using addParametersConverter(). The following example adds an audience parameter to the request when the registrationId is keycloak:

Include Additional Request Parameters
  • Java

  • Kotlin

WebClientReactiveTokenExchangeTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveTokenExchangeTokenResponseClient();
accessTokenResponseClient.addParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
	if (clientRegistration.getRegistrationId().equals("keycloak")) {
		parameters.set(OAuth2ParameterNames.AUDIENCE, "my-audience");
	}
	return parameters;
});
val accessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient()
accessTokenResponseClient.addParametersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "keycloak") {
        parameters[OAuth2ParameterNames.AUDIENCE] = "my-audience"
	}
	parameters
}

You can override default parameters using setParametersConverter(). The following example overrides the client_id parameter when the registrationId is okta:

Override Request Parameters
  • Java

  • Kotlin

WebClientReactiveTokenExchangeTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveTokenExchangeTokenResponseClient();
accessTokenResponseClient.setParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	LinkedMultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
	if (clientRegistration.getRegistrationId().equals("okta")) {
		parameters.set(OAuth2ParameterNames.CLIENT_ID, "my-client");
	}
	return parameters;
});
val accessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient()
accessTokenResponseClient.setParametersConverter { grantRequest ->
    val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "okta") {
        parameters[OAuth2ParameterNames.CLIENT_ID] = "my-client"
	}
	parameters
}

You can fully customize parameters (including omitting default parameters) using setParametersCustomizer(). The following example omits the client_id parameter when the client_assertion parameter is present in the request:

Omit Request Parameters
  • Java

  • Kotlin

WebClientReactiveTokenExchangeTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveTokenExchangeTokenResponseClient();
accessTokenResponseClient.setParametersCustomizer(parameters -> {
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID);
	}
});
val accessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient()
accessTokenResponseClient.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

Customizing the Access Token Response

WebClientReactiveTokenExchangeTokenResponseClient provides hooks for customizing the OAuth 2.0 Access Token Response.

Customizing Response Parameters

You can customize the conversion of Token Response parameters to an OAuth2AccessTokenResponse by calling setBodyExtractor(). The default implementation provided by OAuth2BodyExtractors.oauth2AccessTokenResponse() parses the response and handles errors accordingly.

The following example provides a starting point for customizing the conversion of Token Response parameters to an OAuth2AccessTokenResponse:

Customize Body Extractor
  • Java

  • Kotlin

WebClientReactiveTokenExchangeTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveTokenExchangeTokenResponseClient();

BodyExtractor<Mono<Map<String, Object>>, ReactiveHttpInputMessage> bodyExtractor =
	BodyExtractors.toMono(new ParameterizedTypeReference<>() {});
accessTokenResponseClient.setBodyExtractor((inputMessage, context) ->
	bodyExtractor.extract(inputMessage, context)
		.map(parameters -> OAuth2AccessTokenResponse.withToken("custom-token")
			// ...
			.build()
		)
);
val accessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient()

val bodyExtractor = BodyExtractors.toMono(object : ParameterizedTypeReference<Map<String, Any>>() {})
accessTokenResponseClient.setBodyExtractor { inputMessage, context ->
	bodyExtractor.extract(inputMessage, context).map { parameters ->
		OAuth2AccessTokenResponse.withToken("custom-token")
			// ...
			.build()
	}
}

When providing a custom BodyExtractor, you are responsible for detecting and converting an OAuth 2.0 Error Response to a Mono.error() with OAuth2Error based on parameters of the response.

Customizing the WebClient

Alternatively, if your requirements are more advanced, you can take full control of the request and/or response by providing a pre-configured WebClient to setWebClient() as the following example shows:

Customize WebClient
  • Java

  • Kotlin

WebClient webClient = WebClient.builder()
	// ...
	.build();

WebClientReactiveTokenExchangeTokenResponseClient accessTokenResponseClient =
	new WebClientReactiveTokenExchangeTokenResponseClient();
accessTokenResponseClient.setWebClient(webClient);
val webClient = WebClient.builder()
	// ...
	.build()

val accessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient()
accessTokenResponseClient.setWebClient(webClient)

Customize using the Builder

Whether you customize WebClientReactiveTokenExchangeTokenResponseClient or provide your own implementation of ReactiveOAuth2AccessTokenResponseClient, you can configure it using the ReactiveOAuth2AuthorizedClientProviderBuilder (as an alternative to publishing a bean) as follows:

Access Token Response Configuration via Builder
  • Java

  • Kotlin

// Customize
ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeTokenResponseClient = ...

TokenExchangeReactiveOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = new TokenExchangeReactiveOAuth2AuthorizedClientProvider();
tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeTokenResponseClient);

ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
		ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
				.provider(tokenExchangeAuthorizedClientProvider)
				.build();

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// Customize
val tokenExchangeTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> = ...

val tokenExchangeAuthorizedClientProvider = TokenExchangeReactiveOAuth2AuthorizedClientProvider()
tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeTokenResponseClient)

val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
        .provider(tokenExchangeAuthorizedClientProvider)
        .build()

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

Using the Access Token

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

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: urn:ietf:params:oauth:grant-type:token-exchange
            scope: read
        provider:
          okta:
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

…​and the OAuth2AuthorizedClientManager @Bean:

  • Java

  • Kotlin

@Bean
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
		ReactiveClientRegistrationRepository clientRegistrationRepository,
		ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {

	TokenExchangeReactiveOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider =
			new TokenExchangeReactiveOAuth2AuthorizedClientProvider();

	ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
			ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
					.provider(tokenExchangeAuthorizedClientProvider)
					.build();

	DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
			new DefaultReactiveOAuth2AuthorizedClientManager(
					clientRegistrationRepository, authorizedClientRepository);
	authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

	return authorizedClientManager;
}
@Bean
fun authorizedClientManager(
        clientRegistrationRepository: ReactiveClientRegistrationRepository,
        authorizedClientRepository: ServerOAuth2AuthorizedClientRepository): ReactiveOAuth2AuthorizedClientManager {
    val tokenExchangeAuthorizedClientProvider = TokenExchangeReactiveOAuth2AuthorizedClientProvider()
    val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
            .provider(tokenExchangeAuthorizedClientProvider)
            .build()
    val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientRepository)
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
    return authorizedClientManager
}

You may obtain the OAuth2AccessToken as follows:

  • Java

  • Kotlin

@RestController
public class OAuth2ResourceServerController {

	@Autowired
	private ReactiveOAuth2AuthorizedClientManager authorizedClientManager;

	@GetMapping("/resource")
	public Mono<String> resource(JwtAuthenticationToken jwtAuthentication) {
		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
				.principal(jwtAuthentication)
				.build();

		return this.authorizedClientManager.authorize(authorizeRequest)
				.map(OAuth2AuthorizedClient::getAccessToken)
				// ...
				.thenReturn("index");
	}
}
class OAuth2ResourceServerController {

    @Autowired
    private lateinit var authorizedClientManager: ReactiveOAuth2AuthorizedClientManager

    @GetMapping("/resource")
    fun resource(jwtAuthentication: JwtAuthenticationToken): Mono<String> {
        val authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
                .principal(jwtAuthentication)
                .build()
        return authorizedClientManager.authorize(authorizeRequest)
                .map { it.accessToken }
                // ...
                .thenReturn("index")
    }
}

TokenExchangeReactiveOAuth2AuthorizedClientProvider resolves the subject token (as an OAuth2Token) via OAuth2AuthorizationContext.getPrincipal().getPrincipal() by default, hence the use of JwtAuthenticationToken in the preceding example. An actor token is not resolved by default.

If you need to resolve the subject token from a different source, you can provide TokenExchangeReactiveOAuth2AuthorizedClientProvider.setSubjectTokenResolver() with a custom Function<OAuth2AuthorizationContext, Mono<OAuth2Token>>.

If you need to resolve an actor token, you can provide TokenExchangeReactiveOAuth2AuthorizedClientProvider.setActorTokenResolver() with a custom Function<OAuth2AuthorizationContext, Mono<OAuth2Token>>.