Introduction
OCI IAM Workload Identity Federation (WIF) allows external workloads to securely exchange third-party JWTs (JSON Web Tokens) for OCI-issued User Principal Session Tokens (UPSTs).
A UPST is a short-lived OCI security token that enables a workload to act as an OCI principal without requiring long-lived OCI credentials. Unlike a standard JWT, a UPST also contains the cryptographic material required to sign OCI API requests, making it suitable for direct OCI API access and SDK usage.
Additionally, OCI WIF supports impersonation scenarios, allowing external identities to assume the permissions of OCI service users.
Several articles and examples already exist for integrations with Terraform, GitHub Actions, Keycloak, and other identity providers. This blog focuses specifically on using a Microsoft Entra ID application to obtain an access token and exchange it for an OCI UPST.
While the overall architecture appears straightforward, the initial setup often presents a few unexpected challenges. OCI IAM, Microsoft Entra ID, and Keycloak evolve continuously, and troubleshooting low-level token validation issues can become time-consuming.
For this reason, the recommended approach is to start with a simple, coarse-grained configuration that proves the end-to-end flow. Once the exchange is working successfully, the configuration can be refined with more restrictive trust rules and claim mappings.
This approach is particularly useful for engineers who need a working solution quickly without spending hours debugging token internals.
High-Level Flow with EntraID
At a high level, the process consists of the following steps:
- Create OCI IAM Domain Confidential Applications
- One application for trust administration
- One application for performing the token exchange
- (Optional) Create an OCI Service User
- Required if impersonation is used
- Create a Microsoft Entra ID Application
- Create an OCI Identity Propagation Trust Configuration
- Generate a public/private key pair
- Request a JWT from Microsoft Entra ID
- Exchange the JWT for an OCI UPST
For most OCI administrators, step 3 is where the first unexpected hurdle appears.
The Microsoft Entra ID Challenge
A newly created Entra ID application typically uses Microsoft Graph as its default API scope.
Depending on the application configuration and requested scope, Entra ID may issue access tokens that include additional header attributes such as nonce.. While Microsoft can validate these tokens correctly, some external systems expect a standard JWT signature validation flow and may reject the token.
A JWT header obtained from Microsoft Graph commonly looks like this:
{
"typ": "JWT",
"nonce": "Fbt1NVvCAfKzOzJxUOFcOGDHZ9tHTeDO4lzmaYTUm_I",
"alg": "RS256",
"x5t": "wh06sEkzLHJ5sNNaU...",
"kid": "wh06sEkzLHJ5sNNaU..."
}
This token will typically fail OCI trust validation.
Instead, create a custom (dummy) API within the Entra ID application and grant the application access to this API.
The resulting JWT header will look like this:
{
"typ": "JWT",
"alg": "RS256",
"x5t": "wh06sEkzLHJ5sNNaU...",
"kid": "wh06sEkzLHJ5sNNaU..."
}
This is the expected format and can be successfully validated by OCI.
Requesting the Entra ID Access Token
The correct token request uses the custom API scope:
curl --location 'https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=<client-id>' \
--data-urlencode 'client_secret=<client-secret>' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=api://<client-id>/.default'
Avoid using the Microsoft Graph scope: scope=https://graph.microsoft.com/.default
Using the Graph scope results in OCI:
{
"error": "unauthorized_client",
"error_description": "Invalid signed JWT assertion."
}
because the JWT signature cannot be validated as expected.
The Entra App may look like this to show custom Api permissions which need to be requested in token call:

Preparing the OCI Trust Configuration
Before continuing, review the official OCI documentation:
- OCI Documentation:
https://docs.oracle.com/en-us/iaas/Content/Identity/api-getstarted/json_web_token_exchange.htm - In addition Oracle A-Team Overview:
https://www.ateam-oracle.com/workload-identity-federation
Both references describe multiple deployment patterns. This article focuses on the simplest configuration that can later be refined.
To create the trust configuration, you need:
- OCI IAM Domain Administrator privileges
- An administrative OAuth token for the OCI Identity Domain
- The Entra ID issuer URL
- The Entra ID signing key endpoint
Using the signing key endpoint is recommended because key rotation on the Microsoft side is handled automatically.
Creating the Identity Propagation Trust
A minimal trust using impersonation can look like this:
Create the trust using:
POST <IAM-DOMAIN-URL>/admin/v1/IdentityPropagationTrusts
Example payload:
{
"active": true,
"allowImpersonation": true,
"issuer": "https://sts.windows.net/<tenant-id>/",
"name": "Azure Token Trust JWT to UPST with Impersonation",
"oauthClients": [
"<OAuth Client ID>"
],
"publicKeyEndpoint": "https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys",
"impersonationServiceUsers": [
{
"rule": "sub eq *",
"value": "<service-user-id>"
}
],
"subjectType": "User",
"type": "JWT",
"schemas": [
"urn:ietf:params:scim:schemas:oracle:idcs:IdentityPropagationTrust"
]
}
Notes
The example above maps all incoming identities to a single OCI service user:
"rule": "sub eq *"
For production environments, consider using more specific claim mappings.
For example:
"rule": "oid eq <Entra-Object-ID>"
Additional matching expressions and claim mappings are documented in the OCI Identity Domains documentation.
Exchanging the JWT for an OCI UPST
The token exchange call is performed against:
POST <IAM-DOMAIN-URL>/oauth2/v1/token
Example request:
curl --location '<IAM-DOMAIN-URL>/oauth2/v1/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic <base64-client-credentials>' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'requested_token_type=urn:oci:token-type:oci-upst' \
--data-urlencode 'public_key=<public-key>' \
--data-urlencode 'subject_token=<entra-access-token>' \
--data-urlencode 'subject_token_type=jwt'
If everything is configured correctly, OCI returns a UPST that can be used with OCI SDKs and APIs.
For problem troubleshooting see section Common Errors.
High-Level Flow with Keycloak
While this blog focuses on Microsoft Entra ID, OCI Workload Identity Federation works with any standards-compliant OpenID Connect (OIDC) provider capable of issuing signed JWTs. Keycloak is an excellent example because it provides full control over token issuance, claims, signing keys, and service accounts.
Unlike Entra ID, Keycloak provides full control over token content and signing configuration. No custom API scope is required, and custom claims can be added directly through Protocol Mappers.
The overall flow remains identical:
- Configure a Keycloak Realm
- Create a confidential client
- Enable a service account
- Configure OCI Identity Propagation Trust
- Obtain a JWT from Keycloak
- Exchange the JWT for an OCI UPST
- Use the UPST with OCI SDKs or APIs
Keycloak Setup: For this example, assume a Realm called oci-demo and a Client called oci-wif-client
Understanding the Keycloak JWT
When using the Client Credentials flow, Keycloak issues a JWT similar to:
{
"exp": 1711234567,
"iat": 1711230967,
"iss": "https://keycloak.example.com/realms/oci-demo",
"aud": "account",
"sub": "d8a6a4cb-6b67-4c4f-8f74-b0ef8d1e4f5f",
"azp": "oci-wif-client",
"preferred_username": "service-account-oci-wif-client"
}
Unlike the Entra ID scenario, no custom API scope is required. Depending on the Keycloak version and client configuration, the audience claim may differ.
Creating the OCI Trust
The OCI trust definition is nearly identical to the Entra ID example.
{
"active": true,
"allowImpersonation": true,
"issuer": "https://keycloak.example.com/realms/oci-demo",
"name": "Keycloak JWT Trust",
"oauthClients": [
"<oauth-client-id>"
],
"publicKeyEndpoint": "https://keycloak.example.com/realms/oci-demo/protocol/openid-connect/certs",
"impersonationServiceUsers": [
{
"rule": "sub eq *",
"value": "<service-user-id>"
}
],
"subjectType": "User",
"type": "JWT",
"schemas": [
"urn:ietf:params:scim:schemas:oracle:idcs:IdentityPropagationTrust"
]
}
Obtaining a JWT from Keycloak
Request an access token using the Client Credentials flow:
curl --location \
'https://keycloak.example.com/realms/oci-demo/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=oci-wif-client' \
--data-urlencode 'client_secret=<client-secret>'
The returned access token becomes the subject_token for OCI token exchange.
Exchanging the JWT for an OCI UPST
The OCI token exchange call is identical regardless of the identity provider.
curl --location '<IAM-DOMAIN-URL>/oauth2/v1/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic <base64-client-id:secret>' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'requested_token_type=urn:oci:token-type:oci-upst' \
--data-urlencode 'public_key=<public-key>' \
--data-urlencode 'subject_token=<keycloak-access-token>' \
--data-urlencode 'subject_token_type=jwt'
If successful, OCI returns a UPST that can be used with OCI SDKs and APIs.
Leveraging Keycloak Claim Mappers
One advantage of Keycloak is the ability to control token content through Protocol Mappers.
For example, you might add a custom claim:
{
" oci_role": "network-admin"
}
and then use it in OCI mapping rules:
{
"rule": "oci_role eq network-admin",
"value": "<service-user-id>"
}
This allows multiple workloads to share the same Keycloak realm while being mapped to different OCI service users. For production deployments, this is usually cleaner than mapping everything based solely on sub.
Keycloak-Specific Troubleshooting
Issuer mismatch
Ensure the JWT issuer exactly matches:
https://keycloak.example.com/realms/oci-demo
Even a missing realm name causes OCI trust lookup failures.
Wrong cert endpoint
Correct:
https://keycloak.example.com/realms/oci-demo/protocol/openid-connect/certs
Incorrect:
https://keycloak.example.com/realms/oci-demo
OCI must be able to retrieve the JWKS keys.
Service account disabled
Client Credentials flow requires:
Service Accounts Enabled = ON
Otherwise token requests fail before OCI is involved.
Pre-Validation Checklist (Entra ID and keycloak)
Before troubleshooting OCI, validate the JWT independently.
Verify the JWT
Using a JWT validation tool which is not the one from Microsoft:
- Verify that iss matches the trust configuration exactly.
- Verify that claims required by the impersonation rule are present.
- Verify that the signing key endpoint is reachable.
- Verify that the JWT header contains no nonce.
- Verify that the kid value exists in the published signing keys.
- Verify that the JWT signature validates successfully.
Verify OCI Configuration
- Ensure only one Identity Propagation Trust exists for the issuer.
- Confirm that the trust is active.
- Confirm that the OAuth client is associated with the trust.
- Confirm that the OAuth clients are active
Common Error Messages
Expired Access Token
{
"error": "unauthorized_client",
"error_description": "Expired input JWT assertion."
}
Cause
Expired source access token.
Solution
Request a fresh token and retry the exchange.
Incorrect Signing Key Configuration
{
"error": "invalid_request",
"error_description": "Could not parse certificate: java.io.IOException: Empty input"
}
Cause
The trust configuration references the key endpoint as a certificate instead of a key endpoint.
Incorrect (sample Entra ID)
"publicCertificate": "https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys"
Correct
"publicKeyEndpoint": "https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys"
Public Key Formatting Error
{
"error": "invalid_request",
"error_description": "The request contains invalid or missing parameters or duplicate values."
}
Cause
The public key supplied during token exchange contains PEM headers.
Incorrect
-----BEGIN PUBLIC KEY-----
MIIBI...
-----END PUBLIC KEY-----
Correct
MIIBI...
Only provide the Base64-encoded key content.
Invalid JWT Signature
{
"error": "unauthorized_client",
"error_description": "Invalid signed JWT assertion."
}
Cause
Most commonly caused by the Entra ID application using Microsoft Graph scopes, resulting in a token format OCI cannot validate.
Solution
Use a custom API scope instead of https://graph.microsoft.com/.default
Issuer Mismatch
{
"error": "unauthorized_client",
"error_description": "Unique Identity Trust Configuration not found for provided issuer."
}
Cause
OCI could not uniquely identify a trust configuration.
This typically happens when:
- The issuer value does not match exactly.
- No trust exists for the issuer.
- Multiple trusts exist for the same issuer.
Invalid ClientID for Token exchange
{
"error": "invalid_client",
"error_description": "Client authentication failed."
}
Cause
The OAuth client used for token exchange is either invalid or not associated with the Identity Propagation Trust.
This typically happens when:
- Miss-spelling (check trust definition)
- Used App-ID instead of OAuth Client ID
Diagnostics and Troubleshooting
OCI Identity Domains provides built-in diagnostics that can significantly simplify troubleshooting.
Diagnostics can be enabled in:
Identity Domain → Settings → Diagnostics
Once enabled, diagnostics are typically enabled for a limited time window (currently 15 minutes) and can be stopped manually..
Diagnostic information can be retrieved through:
- Identity Domain Reports
- REST API (/admin/v1/DiagnosticRecords/.search)
When troubleshooting trust validation, diagnostics often reveal the exact validation step that failed and should be one of the first tools used when token exchange errors occur.
Conclusion
OCI Workload Identity Federation provides a powerful mechanism for integrating external identity providers with OCI while avoiding long-lived credentials.
Although the overall architecture is straightforward, the integration contains a few non-obvious pitfalls, particularly around token scopes and JWT signature validation. By starting with a minimal trust configuration, validating tokens independently, and using OCI diagnostics when necessary, you can establish a working JWT-to-UPST exchange quickly and then gradually refine the trust model for production use.