Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreI'm excited to share that there will be support for the OAuth 2.0 Token Exchange Grant (RFC 8693) in Spring Security 6.3, which is available for preview now in the latest milestone (6.3.0-M3). This support provides the ability to use Token Exchange with OAuth2 Client. Similarly, server-side support is also shipping with Spring Authorization Server in 1.3 and is available for preview now in the latest milestone (1.3.0-M3).
OAuth2 Client features of Spring Security allow us to easily make protected resources requests to an API secured with OAuth2 bearer tokens. Similarly, OAuth2 Resource Server features of Spring Security allow us to secure an API with OAuth2. Let's take a look at how we can use the new support to build OAuth2 flows with Token Exchange.
Let's imagine we have a resource server called user-service
providing an API to access user information. In order to make requests to user-service
, clients must provide an access token. Let's assume tokens must have an audience (aud
claim) of user-service
. This might look like the following as Spring Boot configuration properties:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://my-auth-server.com
audiences: user-service
Now let's imagine we want to introduce a new resource server called message-service
and call it from user-service
. Let's assume then that tokens for this new service must have an audience of message-service
. Clearly we can't re-use the token from a request to user-service
in a request to message-service
. However, we'd like the identity of the user from the original request to be preserved. How would we accomplish this?
In order to obtain the necessary access token for message-service
, the resource server user-service
must become a client and exchange an existing token for a new one that retains the identity (user) of the original token. This is called "impersonation" and is exactly the kind of scenario OAuth 2.0 Token Exchange is designed for.
To enable Token Exchange, we need to configure user-service
to act as both a resource server and a client that can use Token Exchange, as the following Spring Boot configuration properties show:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://my-auth-server.com
audiences: user-service
client:
registration:
my-token-exchange-client:
provider: my-auth-server
client-id: token-client
client-secret: token
authorization-grant-type: urn:ietf:params:oauth:grant-type:token-exchange
client-authentication-method: client_secret_basic
scope:
- message.read
provider:
my-auth-server:
issuer-uri: https://my-auth-server.com
We also need to enable the use of the new grant type in Spring Security, which we can do by publishing the following bean:
@Bean
public OAuth2AuthorizedClientProvider tokenExchange() {
return new TokenExchangeOAuth2AuthorizedClientProvider();
}
This is all that is required to begin using Token Exchange. However, if we want to request a specific audience
or resource
value, we need to configure additional parameters as part of the token request, as the following example shows:
@Bean
public OAuth2AuthorizedClientProvider tokenExchange() {
var requestEntityConverter = new TokenExchangeGrantRequestEntityConverter();
requestEntityConverter.addParametersConverter((grantRequest) -> {
var parameters = new LinkedMultiValueMap<String, String>();
parameters.add(OAuth2ParameterNames.AUDIENCE, "message-service");
parameters.add(OAuth2ParameterNames.RESOURCE, "https://example.com/messages");
return parameters;
});
var accessTokenResponseClient = new DefaultTokenExchangeTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);
var authorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
return authorizedClientProvider;
}
With this configuration in place, we can obtain an access token in one resource server and use it as a Bearer
token in a protected resources request to another resource server. The original bearer token passed to the resource server in the Authorization
header will be used by default to obtain the new access token.
TIP: See Authorized Client Features in the reference documentation for more information on how to obtain an access token and make a protected resources request with this configuration.
To complete the picture, let's build a brand new authorization server application with Spring Authorization Server to support this flow.
Using Spring Initializr with the OAuth2 Authorization Server dependency, we can configure a fully functional authorization server using the following Spring Boot configuration properties:
spring:
security:
user:
name: sally
password: password
oauth2:
authorizationserver:
client:
test-client:
registration:
client-id: test-client
client-secret: {noop}secret
client-authentication-methods:
- client_secret_basic
authorization-grant-types:
- authorization_code
- refresh_token
scopes:
- user.read
token-client:
registration:
client-id: token-client
client-secret: {noop}token
client-authentication-methods:
- client_secret_basic
authorization-grant-types:
- urn:ietf:params:oauth:grant-type:token-exchange
scopes:
- message.read
As with the client, we may want to support specific request parameters for Token Exchange such as audience
or resource
, which we can do by publishing the following bean:
@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> accessTokenCustomizer() {
return (context) -> {
if (AuthorizationGrantType.TOKEN_EXCHANGE.equals(context.getAuthorizationGrantType())) {
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = context.getAuthorizationGrant();
Set<String> resources = tokenExchangeAuthentication.getResources();
// TODO: Validate resource value(s) and map to the
// appropriate audience value(s) if needed...
context.getClaims().audience(...);
}
};
}
With this configuration in place, the authorization server supports the Token Exchange grant with the optional resource
parameter of the OAuth 2.0 Token Request, and is able to issue tokens allowing a resource server to act as a client and impersonate an end user.
In this blog post, we have discussed the "impersonation" use case for Token Exchange and explored a simple configuration for both a resource server (acting as a client) and an authorization server.
TIP: See Appendix A of RFC 8693 for additional examples including an example of an additional use case called "delegation" which is also supported.
I hope you are as excited as I am about this new support! I encourage you to try out the samples in Spring Authorization Server which includes a working example of this blog post. Please also try the milestones of both Spring Security and Spring Authorization Server in your own project. We would love your feedback!