OBO SSO in Java Applications: Securely Calling Downstream APIs on Behalf of a User
OBO (On-Behalf-Of) allows a Java API to securely call downstream services using the authenticated user's identity instead of the application's identity.
Join the DZone community and get the full member experience.
Join For FreeModern enterprise applications rarely operate in isolation. A user may authenticate through a web or mobile application, invoke a Java-based backend API, and that backend may need to call additional downstream services such as microservices or third-party APIs.
In these scenarios, simply using the application's identity is often insufficient. The downstream service may need to know which user initiated the request and enforce authorization based on that user's permissions. This is where the OAuth 2.0 On-Behalf-Of (OBO) flow becomes invaluable.
In this article, I will summarize how the OBO flow works, where it fits in a modern Java architecture, and how to implement it securely in a Spring Boot application.
How Does Each Downstream Service Know Who the Original User Is?
One of the first assumptions many engineers make is that the backend can simply reuse its own application credentials when communicating with another service. While this works for machine-to-machine communication, it falls short whenever user-specific authorization is required.
Consider a healthcare application where a physician logs into a patient portal and requests medical records. The initial Java API authenticates the request, but retrieving those records may require calling another internal API responsible for patient information. That downstream API needs to know which physician initiated the request before deciding whether access should be granted. If the Java backend uses only its own application identity, the downstream service loses the user context and cannot perform authorization based on the physician's permissions.
This is exactly the problem that the OAuth 2.0 On-Behalf-Of (OBO) flow was designed to solve.
What Is OBO (On-Behalf-Of) Flow?
The OBO flow allows a middle-tier service (API A) to obtain an access token for another downstream service (API B) while preserving the identity and permissions of the signed-in user.
Instead of API A calling API B using its own application credentials, API A exchanges the user's access token for a new token intended for API B.
The flow looks like this:
User
|
v
Web/Mobile Applications
|
| Access Token
v
Java API A
|
| OBO Token Exchange
v
Identity Provider
|
| New Access Token
v
Java API B
As a result, API B receives a token representing the actual user, allowing it to perform proper authorization checks.
Why Not Use Client Credentials?
Many developers mistakenly use the Client Credentials flow when calling downstream APIs.
While Client Credentials works for service-to-service communication, it does not carry user context.
Consider a healthcare application like ours:
- Dr. Smith logs into a patient portal.
- The Java API retrieves patient records from another service.
- The downstream service must verify Dr. Smith's permissions.
If Client Credentials is used, the downstream service only sees the application identity and loses visibility into the actual user making the request.
OBO solves this problem by preserving delegated permissions.
Typical Enterprise Use Cases
OBO is commonly used for Healthcare applications accessing patient records, Enterprise microservices, Multi-tier API architectures, internal service authorization, and audit and compliance requirements.
Many organizations implementing zero-trust architectures rely heavily on delegated authorization models such as OBO.
Implementing OBO in Spring Boot
Let's assume the following:
- Microsoft Entra ID (Azure AD) is the Identity Provider.
- API A is a Spring Boot application.
- API B is a downstream service.
Step 1: Add MSAL4J Dependency
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.15.0</version>
</dependency>
Step 2: Acquire Token On Behalf Of User
The incoming access token is received from the frontend application.
String clientId = "YOUR_CLIENT_ID";
String clientSecret = "YOUR_CLIENT_SECRET";
IClientCredential credential =
ClientCredentialFactory.createFromSecret(clientSecret);
ConfidentialClientApplication app =
ConfidentialClientApplication.builder(
clientId,
credential)
.authority(
"https://login.microsoftonline.com/TENANT_ID")
.build();
UserAssertion userAssertion =
new UserAssertion(incomingUserToken);
OnBehalfOfParameters parameters =
OnBehalfOfParameters.builder(
Collections.singleton("api://api-b/.default"),
userAssertion)
.build();
IAuthenticationResult result =
app.acquireToken(parameters).join();
String downstreamAccessToken =
result.accessToken();
At this point, the Java application has obtained a new token that can be used to call API B while preserving the user's identity.
Step 3: Call the Downstream API
Using Spring's RestTemplate:
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(downstreamAccessToken);
HttpEntity<Void> request =
new HttpEntity<>(headers);
ResponseEntity<String> response =
restTemplate.exchange(
"https://api-b.company.com/patients",
HttpMethod.GET,
request,
String.class);
return response.getBody();
API B now receives a delegated token representing the authenticated user.
Security Best Practices
Implementing OBO correctly is critical.
1. Validate Incoming Tokens
Always validate:
- Signature
- Issuer
- Audience
- Expiration
Never trust tokens received from clients without validation.
2. Apply Least Privilege
Only request scopes required by the downstream API.
Bad:
https://graph.microsoft.com/.default
Better:
User.Read
Limiting scopes reduces the blast radius if a token is compromised.
3. Never Log Access Tokens
Avoid:
logger.info(token);
Access tokens often contain sensitive claims and permissions.
4. Secure Client Secrets
Store secrets in:
- Azure Key Vault
- AWS Secrets Manager
- HashiCorp Vault
Avoid storing secrets in:
application.properties
or source code repositories.
5. Implement Token Caching
Repeated token acquisition creates unnecessary latency. Consider caching OBO tokens until they expire.
Most enterprise identity libraries already provide token caching support.
Common Mistakes
Some common issues I frequently encounter when onboarding new developers include:
- Using Client Credentials instead of OBO
- Passing user tokens directly to downstream APIs
- Requesting excessive scopes
- Logging JWT tokens
- Not validating token audiences
- Hardcoding client secrets
These mistakes often lead to authorization failures or security vulnerabilities.
Conclusion
As organizations adopt microservices and API-first architectures, preserving user identity across service boundaries becomes increasingly important. The OAuth 2.0 On-Behalf-Of flow provides a secure and standards-based approach for allowing Java applications to call downstream APIs while maintaining the original user's context and permissions.
By implementing OBO correctly, developers can build applications that are more secure, auditable, and aligned with modern zero-trust security principles. For enterprise Java teams, understanding OBO is no longer optional; it is becoming a fundamental requirement for building secure distributed systems.
Opinions expressed by DZone contributors are their own.
Comments