OAuth 2.0 token exchange

Token exchange (RFC 8693) is an extension to OAuth 2.0 for implementing scenarios where one token needs to be swapped for another.

Some scenarios that may involve a token exchange:

Scenario Example
(on behalf of)
Issuing a service that is fulfilling some token-authorised request with a token to access a backend resource
(act as)
Issuing a super user with a token to access a resource as some regular user

The exchange occurs at the standard token endpoint of an authorisation server, with a special grant type (urn:ietf:params:oauth:grant-type:token-exchange) established for the purpose.

The exchange protocol is designed for maximum flexibility. The submitted token and the newly minted token to be of any type:

  • OAuth 2.0: Access token, refresh token
  • OpenID Connect: ID token
  • SAML 1.1 or 2.0 assertion
  • Some arbitrary JWT
  • Some other type of token

The following examples assume v9.17 of the SDK.

Access token exchange

The following snippet reproduces the example from the token exchange specification where a service needs to obtain a downstream token for some backend:

import java.net.*;
import java.util.*;
import com.nimbusds.oauth2.sdk.*;
import com.nimbusds.oauth2.sdk.auth.*;
import com.nimbusds.oauth2.sdk.http.*;
import com.nimbusds.oauth2.sdk.id.*;
import com.nimbusds.oauth2.sdk.token.*;
import com.nimbusds.oauth2.sdk.tokenexchange.*;

// The client credentials for a basic authentication
ClientID clientID = new ClientID("rs08");
Secret clientSecret = new Secret("eij8teegie3aequuQu9quahp7Vea7ohf");
ClientSecretBasic clientSecretBasic = new ClientSecretBasic(clientID, clientSecret);

// The upstream access token (must have been validated)
AccessToken accessToken = new BearerAccessToken("accVkjcJyb4BWCxGsndESCJQbdFMogUC5PbRDqceLTC");

// Compose the token exchange request
URI tokenEndpoint = new URI("https://as.example.com/as/token.oauth2");
URI resource = new URI("https://backend.example.com/api");
Scope scope = null; // default scope for resource
TokenRequest tokenRequest = new TokenRequest(
    new TokenExchangeGrant(

// Send the token request
HTTPRequest httpRequest = tokenRequest.toHTTPRequest();
HTTPResponse httpResponse = httpRequest.send();

// Parse the token response
TokenResponse tokenResponse = TokenResponse.parse(httpResponse);

if (! tokenResponse.indicatesSuccess()) {
    // The token request failed
    ErrorObject errorObject = tokenResponse.toErrorResponse().getErrorObject();

AccessTokenResponse tokenSuccessResponse = tokenResponse.toSuccessResponse();

// Expecting access token of type Bearer
AccessToken downstreamToken = tokenSuccessResponse.getTokens().getAccessToken();

if (! AccessTokenType.BEARER.equals(downstreamToken.getType()) &&
    ! TokenTypeURI.ACCESS_TOKEN.equals(downstreamToken.getIssuedTokenType())) {
    // Unexpected token type
    System.out.println("Received unexpected token: " + downstreamToken.getIssuedTokenType());

// Use the downstream token...