"On-Behalf-Of" (OBO) implementation in Java okta-spring-boot-starter

My understanding is that for a machine to machine application, an OBO ‘on-behalf-of’ flow is good practice.

Thus what is the correct way to implement the OBO, ‘on-behalf-of’ flow for a Java implementation using the okta-spring-boot-starter library?
Whereupon, a JWT will already be available in the security context, then that JWT can be used to call the Auth0 token endpoint to get another JWT to then call a downstream service, with all claims intact.

Is there an example that I have missed?

Hi @dmiller,

Welcome to the Auth0 Community!

The “On-Behalf-Of” (OBO) pattern is indeed a best practice for preserving user identity across service boundaries. While okta-spring-boot-starter simplifies resource server configuration, it doesn’t provide a “one-click” OBO implementation because Auth0 handles this via the OAuth 2.0 Token Exchange (RFC 8693) or Client Credentials flow, depending on whether you need to strictly maintain the original user’s identity or just the service’s authority.

Since you are using Auth0, the modern way to achieve this is through Token Exchange.

1. Enable Token Exchange in Auth0

You must first ensure your “API A” (the Service) is allowed to perform the exchange.

  • Go to Auth0 Dashboard > Applications.
  • Select your Machine-to-Machine application representing “API A”.
  • In Advanced Settings > Grant Types, ensure Token Exchange is enabled.

2. Implementation in Java (Spring Boot)

You can use the WebClient or RestTemplate to perform the exchange. The okta-spring-boot-starter manages the incoming token, but you’ll need a small helper to get the outgoing token.

@Service
public class DownstreamService {

    @Value("${okta.oauth2.issuer}")
    private String issuer;

    @Value("${okta.oauth2.client-id}")
    private String clientId;

    @Value("${okta.oauth2.client-secret}")
    private String clientSecret;

    public String getOnBehalfOfToken() {
        // 1. Get the current user's JWT from the Security Context
        JwtAuthenticationToken authentication = (JwtAuthenticationToken) 
            SecurityContextHolder.getContext().getAuthentication();
        String subjectToken = authentication.getToken().getTokenValue();

        // 2. Exchange it for a token scoped for the downstream API
        WebClient webClient = WebClient.builder().baseUrl(issuer).build();

        return webClient.post()
            .uri("/oauth/token")
            .contentType(MediaType.APPLICATION_FORM_URLENCODED)
            .body(BodyInserters.fromFormData("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange")
                .with("subject_token", subjectToken)
                .with("subject_token_type", "urn:ietf:params:oauth:token-type:access_token")
                .with("client_id", clientId)
                .with("client_secret", clientSecret)
                .with("audience", "https://your-downstream-api-identifier")
                .with("scope", "read:data")) // Requested scopes for API B
            .retrieve()
            .bodyToMono(TokenResponse.class)
            .block()
            .getAccessToken();
    }
}

If you have any further questions, please don’t hesitate to reach out.

Have a good one,
Vlad

1 Like

Thanks @vlad.murarasu ,

Much appreciated.

One thing though, I don’t have and option for the grant type ‘Token Exchange’

Is there another setting I need to use to enable it?

Thanks

Hi @dmiller,

It should be Client Credentials, but I see it’s greyed out. See the documentation provided in that warning on how to enable it.

Have a good one,
Vlad

Thanks @vlad.murarasu ,

So using an application with client credentials,

I get the following:

{“error”: “invalid_request”,“error_description”: “Invalid subject_token_type.”}

Hi again @dmiller,

I think I made an error in the code snippet. Instead of

it should be

("grant_type", "urn:ietf:params:oauth:grant-type:client-credentials")

I hope this fixes the issue

Have a good one,
Vlad

Hi @vlad.murarasu ,

I have it working, but only with grant_type: "client_credentials"
And it does not return the sub and claims that were specified in the “subject-token" for the token exchange.
Is there another setting I’m missing?