Cross-Organization Service Account Assumption
Configure a service account in one Prism organization to assume the identity of a service account in another organization using OpenID token exchange.
Overview
Prism allows a service account (SA1) in one organization (Org 1) to present its JWT token to obtain a new token for a service account (SA2) in a different organization (Org 2). This is useful when cross-organization automation needs an identity in a target org without sharing long-lived credentials.
The trust relationship is configured on the target service account: SA2 in Org 2 declares which external issuers and claim rules it will accept. The source org has no special configuration.
Prerequisites
- Two Prism organizations already created and logged into
- A project in each organization
- A service account in each organization
- The
prismCLI configured with separate profiles for each org
If you haven't set these up yet, follow the Getting Started guide for each organization using separate CLI profiles.
-
Create organizations, projects, and service accounts
# Create Org 1 prism organizations create \ --name "Org 1" \ --admin-username admin1 \ --admin-password password1 # Create Org 2 prism organizations create \ --name "Org 2" \ --admin-username admin2 \ --admin-password password2 # Log in to each org with a separate profile prism --profile org1 auth login --org org-1 --username admin1 --password password1 prism --profile org2 auth login --org org-2 --username admin2 --password password2 # Create projects prism --profile org1 projects create --name "my-project" prism --profile org2 projects create --name "my-project" # Create service accounts prism --profile org1 service-accounts create --project my-project --name sa1 prism --profile org2 service-accounts create --project my-project --name sa2 -
Determine the issuer URL for Org 1 tokens
The issuer is embedded in every JWT issued by Prism. Its format depends on the server's
PRISM_IDENTITY_DOMAINsetting (default:id.prism.local):https://{org-slug}.{identity_domain} # Example: https://org-1.id.prism.localVerify by getting a token for Org 1 and inspecting its
issclaim:prism --profile org1 auth caller-identityNote The issuer in the OpenID trust configuration must exactly match theissclaim in the token — not just the OIDC discovery endpoint URL. When in doubt, decode an actual token to confirm the value. -
Create an OpenID trust configuration in Org 2
This tells Org 2 to trust tokens signed by Org 1. The
jwks-urlpoints to Org 1's public key endpoint on your Prism server.prism --profile org2 projects openid-configurations create \ --project my-project \ --name "org1-trust" \ --issuer "https://org-1.id.prism.local" \ --jwks-url "https://your-server/api/v1/organizations/org-1/jwks"Replace
org-1with the actual slug andyour-serverwith your Prism server's address. -
Create an API key for SA1 and obtain its canonical subject
The assumption method in Org 2 will match on the subject claim (
sub) of SA1's token. This is SA1's canonical name.# Create an API key for SA1 prism --profile org1 service-accounts api-keys create \ --project my-project \ --service-account-id sa1 \ --description "cross-org key" # Authenticate as SA1 and check the subject claim prism auth sa-login-with-apikey --api-key "prism_<key_value>" prism auth caller-identityThe
subjectfield will look like:sa1@service-accounts.my-project.id.prism.local -
Add an assumption method to SA2 in Org 2
This configures SA2 to accept tokens from the
org1-trustconfiguration where thesubclaim exactly matches SA1's subject.prism --profile org2 service-accounts assumptions add-openid-custom \ --project my-project \ --service-account sa2 \ --configuration org1-trust \ --match "sub:exact:sa1@service-accounts.my-project.id.prism.local"Claim rule options:
sub:exact:value— exact string match (recommended)sub:glob:pattern— glob match, e.g.sa1@service-accounts.*
You can add multiple
--matchrules; all must be satisfied. -
Perform the assumption
SA1 authenticates with Org 1, then presents its token to assume SA2 in Org 2. The
--service-accountflag takes SA2's canonical name (which encodes both the service account name and its project).# Get a token for SA1 (already done in step 4) # Now assume SA2 in Org 2 prism auth sa-assume-openid \ --org org-2 \ --service-account "sa2@service-accounts.my-project.id.prism.local" \ --token "$(prism auth print-access-token)"Or via the API directly:
curl -X POST https://your-server/api/v1/auth/service-accounts/assume/openid \ -H "Content-Type: application/json" \ -d '{ "org": "org-2", "service_account": "sa2@service-accounts.my-project.id.prism.local", "token": "<SA1 token from Org 1>" }' -
Verify the result
On success, Prism returns a JWT for SA2 in Org 2. Decode it to confirm:
iss— should be the Org 2 issuer (https://org-2.id.prism.local)sub— should besa2@service-accounts.my-project.id.prism.localorg— should beorg-2
# Save the returned token and check identity prism auth caller-identity
Troubleshooting
"unauthorized" error
-
Verify the JWKS URL is reachable and contains the signing key:
curl https://your-server/api/v1/organizations/org-1/jwks | jq '.keys[].kid' -
Verify the issuer matches exactly. Decode the token and compare
the
issclaim against the value in the OpenID configuration:echo "<token>" | cut -d. -f2 | base64 -d 2>/dev/null | jq .iss
"service account does not allow OpenID assumption"
-
Check that an assumption method exists on SA2:
prism --profile org2 service-accounts assumptions list \ --project my-project \ --service-account sa2 -
Verify the claim rule matches the token's
subexactly. Usesub:glob:sa1@*for a less strict match while debugging.
Production considerations
- Always use HTTPS URLs for issuer and JWKS endpoints.
- Keep claim rules as specific as possible — prefer
exactoverglob. - Rotate SA API keys regularly and monitor audit events for assumption attempts.
- Tokens expire after 24 hours by default; configure shorter lifetimes for sensitive identities.