Silicon IAMDocs

On-behalf-of access

On-behalf-of (OBO) lets one Application call a registered endpoint on another Application, including one owned by a different organization. IAM mints a proof for one exact downstream request; the audience authenticates and consumes it before doing the work.

Declare and classify endpoints

Every exposed endpoint must include a required boolean critical alongside endpoint_id, path, and metadata. Discover any audience's catalog with obo().endpoints(app_id). Its organization owner/admin controls that catalog and the initial obo_review_message. For critical scope discussions, use application_scopes().request, get, reply, and decide. A denial requires a reason; request and reply notifications reach the relevant administrators.

{"endpoint_id":"invoices.create","path":"/v1/invoices","critical":true,"metadata":{"reason":{"type":"string"}}}

Discover, hash, sign, then exchange

use silicon_iam_client::{Mutation, api::obo::body_sha256, models};

let catalog = caller.obo().endpoints("acme>billing").await?;

// Compute this over the exact downstream bytes, not re-serialized JSON.
let body_digest = body_sha256(body);
let exchanging = Mutation::new();

let request = models::OboExchangeRequest {
    subject_token: subject_access_token.to_owned(),
    org_id: Some("customer".to_owned()),
    audience: "acme>billing".to_owned(),
    endpoint_id: "invoices.create".to_owned(),
    metadata: serde_json::json!({ "reason": "checkout" }),
    request: models::OboExchangeRequestBinding {
        method: "POST".to_owned(),
        body_sha256: body_digest,
    },
};

let proof = caller.obo()
    .exchange_signed(&request, &catalog, &exchanging)
    .await?;

body_sha256 hashes exact bytes and returns the required lowercase digest. exchange_signed generates a fresh Unix-seconds timestamp, selects the endpoint's exact registered path from the supplied catalog, and uses the same Credential::Application secret for Basic authentication and HMAC. It rejects a catalog/audience/endpoint mismatch or a non-canonical method, path, digest, timestamp, or idempotency key before sending. The signature is lowercase HMAC-SHA256 hex over this exact UTF-8 string:

{timestamp}.{UPPERCASE_METHOD}.{registered_path}.{lowercase_body_sha256}.{idempotency_key}

The request body itself never goes to IAM; it travels directly to the audience Application. For a specialized transport, sign_exchange(request, catalog, timestamp, mutation) exposes the same canonical signer and the low-level exchange method remains available. Normal callers should keep signing and sending coupled through exchange_signed.

What the proof is bound to

IAM binds the proof to the source and audience Applications, subject token, actor, organization, endpoint, metadata, method, registered path and exact body digest. It is valid for one verification or at most 60 seconds, whichever comes first. Exchange immediately before the downstream request.

Consume it against the actual request

let verified = audience.obo().verify(
    &models::OboVerifyRequest {
        access_proof: proof.access_proof,
        request: models::OboVerifyRequestBinding {
            method: "POST".to_owned(),
            path: "/v1/invoices".to_owned(),
            body_sha256: body_sha256(actual_body),
        },
    },
).await?;

// Only now execute the operation. `verified` identifies the actor,
// issuer Application, endpoint and authenticated metadata.
// `verified.authorization` is the current delegated membership binding.

verified.authorization binds the represented actor to the current membership ID/version, authorization epoch, organization, audience and testing environment. Its role/tag disclosure is limited to the intersection of the parent token's scopes and the recipient's currently approved scopes: self.membership.read reveals org_role and self.tags.read reveals tags. Null means undisclosed, not baseline member or empty tags. Apply your application's authorization policy to this binding for this exact verified endpoint/request only. Never fill undisclosed fields from a broader cached scope set, promote the actor from an unbound cached role, or reuse a consumed proof's binding on another request.

Recovering an uncertain exchange

Exchange, unlike verification, is idempotent. Recreate a Mutation with the saved IdempotencyKey and repeat the exact subject token, audience, endpoint, metadata, method and body digest while the proof is still valid. Call exchange_signed again so the retry receives a fresh timestamp and signature; those two headers are not idempotency material and an old timestamp can fall outside the 60-second window. Any changed request input returns 409 idempotency_conflict. The client does not store those inputs or retry the exchange automatically.

How the proof reaches the audience

IAM does not prescribe a downstream proof header or body field. The two Applications must agree how to carry access_proof. Regardless of that transport, the audience calculates the verification method, path and digest from the request it actually received.

Failure modes

ConditionMeaningDo
401The Application Basic credential or exchange HMAC is invalid.Correct the credential, canonical string, clock, or signature. Do not retry unchanged.
403The endpoint was not declared or consented, a critical grant is missing, or the subject token no longer authorizes the selected organization. Re-check the selected user organization, current membership, app declaration, critical approval, and user consent.
404 not_foundThe target or endpoint is nonexistent or unavailable in this production/testing environment. Correct the audience and environment. Do not retry unchanged.
409The proof was consumed, or an idempotency key was reused with different exchange input.Do not retry verification. For an exchange conflict, recover the original input or use a new key for a genuinely new operation.
410 proof_expiredThe proof's 60-second life elapsed. Exchange a new proof for a new downstream attempt.
422The metadata or presented request binding does not satisfy the registered contract.Re-read the catalog and compare the actual request.

See the HTTP OBO contract for the complete signature and authorization checks.