Skip to content

How to Configure Spring Authorization Server 1.3 for OAuth 2.0 Token Exchange

I was building a microservices architecture where Service A needed to call Service B on behalf of a user. The frontend had already authenticated the user with our authorization server, but I needed a way to propagate that identity downstream. The OAuth 2.0 Token Exchange grant (RFC 8693) seemed perfect for this - exchange the user’s token for a new token with the right audience. But when I tried to use it with Spring Authorization Server, I got an error:

Error response
{
"error": "unsupported_grant_type",
"error_description": "Unsupported grant type: urn:ietf:params:oauth:grant-type:token-exchange"
}

The authorization server wasn’t configured to accept token exchange requests. Here’s how I fixed it.

Understanding the Problem

OAuth 2.0 Token Exchange (RFC 8693) allows a client to exchange one token for another. This is essential for:

  1. Token propagation - Passing user identity between microservices
  2. Audience restriction - Getting a token scoped for a specific downstream service
  3. Delegation - Representing a user’s identity in a service-to-service call

The grant type URI is urn:ietf:params:oauth:grant-type:token-exchange, and it’s not enabled by default in Spring Authorization Server.

Checking Your Version

First, I verified my Spring Authorization Server version. Token exchange support was added in version 1.3. I checked my pom.xml:

pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
<version>3.5.11</version>
</dependency>

Spring Boot 3.5.x bundles Spring Authorization Server 1.3+, so I was good. If you’re on an older version, you’ll need to upgrade first.

Verifying Token Exchange Support

Before configuring anything, I wanted to confirm that my authorization server actually supported the token exchange grant. I called the OpenID Provider Metadata endpoint:

Check OpenID configuration
curl http://localhost:9001/.well-known/openid-configuration

The response included:

OpenID configuration response
{
"issuer": "http://localhost:9001",
"grant_types_supported": [
"authorization_code",
"refresh_token",
"client_credentials",
"urn:ietf:params:oauth:grant-type:token-exchange"
],
...
}

The urn:ietf:params:oauth:grant-type:token-exchange entry in grant_types_supported confirmed that token exchange was available. But the client still couldn’t use it.

The Missing Configuration

The error occurred because even though the authorization server supported token exchange, my client wasn’t authorized to use that grant type. I needed to register the client with the token exchange grant type explicitly enabled.

I was using YAML-based client registration. Here’s what I had initially:

application.yml (before)
spring:
security:
oauth2:
authorizationserver:
client:
message-service:
registration:
client-id: "message-service"
client-secret: "{noop}token"
client-authentication-methods:
- "client_secret_basic"
authorization-grant-types:
- "client_credentials"
scopes:
- "message:read"

The client only had client_credentials grant type. I added the token exchange grant type:

application.yml (after)
spring:
security:
oauth2:
authorizationserver:
client:
message-service:
registration:
client-id: "message-service"
client-secret: "{noop}token"
client-authentication-methods:
- "client_secret_basic"
authorization-grant-types:
- "client_credentials"
- "urn:ietf:params:oauth:grant-type:token-exchange"
scopes:
- "openid"
- "message:read"

Key changes:

  1. Added urn:ietf:params:oauth:grant-type:token-exchange to authorization-grant-types
  2. Added openid scope to enable ID tokens (useful for token exchange scenarios)

Testing the Configuration

After restarting the authorization server, I tested the token exchange flow. First, I obtained an initial token using the authorization code flow (simulating a user login). Then I exchanged it for a new token:

Token exchange request
curl -X POST http://localhost:9001/oauth2/token \
-u "message-service:token" \
-d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "subject_token=YOUR_SUBJECT_TOKEN" \
-d "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
-d "audience=https://api.example.com"

The response:

Successful token exchange response
{
"access_token": "eyJraWQiOi...",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 300
}

The token exchange worked! The new token had the requested audience and could be used to call downstream services.

Why This Matters

Before Spring Authorization Server 1.3, implementing token exchange required custom grant types and significant boilerplate code. Now it’s built-in:

  1. No custom code - Just configuration
  2. RFC 8693 compliant - Standard protocol, works with any compliant client
  3. Secure by default - Clients must be explicitly authorized for the grant type

Common Mistakes to Avoid

1. Wrong Version

Token exchange requires Spring Authorization Server 1.3+. If you’re on Spring Boot 3.3 or earlier, you might have an older version. Check your dependency tree:

Check dependency version
mvn dependency:tree | grep authorization-server

2. Missing Grant Type in Client Registration

The most common mistake is configuring the authorization server but forgetting to authorize the client. Always include the full grant type URI in the client’s authorization-grant-types:

Correct grant type configuration
authorization-grant-types:
- "urn:ietf:params:oauth:grant-type:token-exchange"

Don’t abbreviate it to just token-exchange.

3. Incorrect Scope Configuration

The token exchange might fail if the requested scope isn’t in the client’s allowed scopes. Make sure your client registration includes all scopes that will be requested during exchange.

Token Exchange vs. On-Behalf-Of Flow

Token Exchange (RFC 8693) is often confused with the On-Behalf-Of flow in Azure AD. While similar in purpose, they use different grant types:

  • Token Exchange: urn:ietf:params:oauth:grant-type:token-exchange
  • On-Behalf-Of: urn:ietf:params:oauth:grant-type:jwt-bearer

Spring Authorization Server supports both, but they serve slightly different use cases.

Impersonation vs. Delegation

Token exchange supports two modes:

  1. Impersonation - The new token represents the original subject
  2. Delegation - The new token indicates it was issued on behalf of another actor

The actor_token parameter in RFC 8693 enables delegation scenarios.

Summary

Spring Authorization Server 1.3 provides native OAuth 2.0 Token Exchange support through RFC 8693. To enable it:

  1. Use Spring Boot 3.5+ (includes Spring Authorization Server 1.3+)
  2. Add urn:ietf:params:oauth:grant-type:token-exchange to your client’s authorization-grant-types
  3. Verify support via /.well-known/openid-configuration
  4. Test with a token exchange request

The configuration is straightforward - no custom grant type implementations needed. For a complete end-to-end example including the client side implementation, see our companion article on OAuth 2.0 Token Exchange patterns.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments