Assume role with web identity
Exchange an identity token issued by a federation provider this platform
trusts for temporary credentials. The result is the same assumed-role
session POST /v1/assume-role mints, and is used the same way.
This request carries no signature, and it is the only
credential-vending call that does not. A federated caller holds no
Basaltic credential yet — that is what the exchange is for — so the token
in the body is the credential being presented. A signature sent anyway
is ignored, and nothing is taken from the request context: role_id and
account_id are read from the body like every other field.
That does not leave the endpoint open. Two independent gates have to pass, and they fail differently.
The token has to verify. This happens before any role is read, so a
forged token never reaches a trust policy. The signature must chain to a
key the provider publishes, the audience must be the one this platform
accepts, and exp must be in the future. A wrong signer, a token minted
for some other consumer, and an expired token all answer 401 with the
same message — the response does not say which check failed.
The role has to agree. Verifying the token establishes who is
calling; it grants nothing. The role named in role_id is assumable only
if its own trust policy admits this identity. Its principals must name
the federation provider, written crn:iam:::oidc-provider/<provider> —
the one case where a trust policy principal is not the caller’s own CRN,
because a federated identity has no CRN and what is trusted is the source
that vouched for it. Every entry in conditions must then hold against
the token’s claims: basalt:webidentity:Subject carries the token’s
sub and basalt:webidentity:Audience its aud, so a role can bind one
identity instead of accepting everything that provider will ever issue. A
condition on a claim the token does not carry fails closed.
A role whose trust policy names no provider therefore cannot be assumed
this way at all, however good the token is. That is the line between the
two failures: 401 means the token is not trustworthy, 403 means it is
and the role still will not have it.
The credentials come back scoped to account_id, carrying the role’s own
permissions. There is no policy field here — unlike
POST /v1/assume-role, a federated session cannot be scoped down at
exchange time, so the role’s attached policies are the whole grant. Size
the role accordingly.
Because it takes no credentials, requests are rate-limited per client IP.
Which providers are trusted is part of the platform’s own configuration. There is no API for registering an identity provider of your own yet, so this operation is live but has no external provider whose tokens it would accept; the roles that use it today are platform-managed.
Body
The exchange a federated caller sends. It carries no signature — the token is the credential — so every field is read from the body and nothing is inferred from the request context.
The identity token to exchange, as a signed JWT. It is verified before
any role is read: the signature must chain to a key the trusted
provider publishes, the audience must be the one this platform was
configured to accept, and exp must be in the future.
"eyJhbGciOiJSUzI1NiIsImtpZCI6..."
The role to assume. Its trust policy has to admit this token — see the operation description.
"7c9e6679-7425-40de-944b-e07fc1f90ae7"
The account the resulting credentials act in — the ownership scope
stamped on the session, the same scope a signed request selects with
X-Account-Id. Mind the difference in form: the header carries the
account handle, this field carries the account's id. Naming an account
does not widen the session; the role's own policies remain the ceiling.
"550e8400-e29b-41d4-a716-446655440000"
A label recorded on the session and in the audit trail. Defaults to the
token's sub claim, so an unnamed session still records which identity
it came from.
"reports-exporter"
Credential validity duration (15 min to 12 hours). A value above the
role's own max_session_duration is rejected rather than clamped.
900 <= x <= 432003600