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

If you haven't set these up yet, follow the Getting Started guide for each organization using separate CLI profiles.

  1. 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
  2. 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_DOMAIN setting (default: id.prism.local):

    https://{org-slug}.{identity_domain}
    # Example: https://org-1.id.prism.local

    Verify by getting a token for Org 1 and inspecting its iss claim:

    prism --profile org1 auth caller-identity
    Note The issuer in the OpenID trust configuration must exactly match the iss claim in the token — not just the OIDC discovery endpoint URL. When in doubt, decode an actual token to confirm the value.
  3. Create an OpenID trust configuration in Org 2

    This tells Org 2 to trust tokens signed by Org 1. The jwks-url points 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-1 with the actual slug and your-server with your Prism server's address.

  4. 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-identity

    The subject field will look like: sa1@service-accounts.my-project.id.prism.local

  5. Add an assumption method to SA2 in Org 2

    This configures SA2 to accept tokens from the org1-trust configuration where the sub claim 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 --match rules; all must be satisfied.

  6. Perform the assumption

    SA1 authenticates with Org 1, then presents its token to assume SA2 in Org 2. The --service-account flag 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>"
      }'
  7. 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 be sa2@service-accounts.my-project.id.prism.local
    • org — should be org-2
    # Save the returned token and check identity
    prism auth caller-identity

Troubleshooting

"unauthorized" error

  1. Verify the JWKS URL is reachable and contains the signing key:
    curl https://your-server/api/v1/organizations/org-1/jwks | jq '.keys[].kid'
  2. Verify the issuer matches exactly. Decode the token and compare the iss claim 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"

  1. Check that an assumption method exists on SA2:
    prism --profile org2 service-accounts assumptions list \
      --project my-project \
      --service-account sa2
  2. Verify the claim rule matches the token's sub exactly. Use sub:glob:sa1@* for a less strict match while debugging.

Production considerations