BetterIAM
Server API

sts

The sts group issues and inspects temporary credentials, the way a cloud security token service does.

The sts group issues and inspects temporary credentials, the way a cloud security token service does. A CLI or a CI job can trade a long-lived credential for a short-lived, narrowed one (getSessionToken), a workload can trade an external OpenID Connect token for a role session without holding any IAM secret (assumeRoleWithWebIdentity), and any holder can ask who a credential acts as (getCallerIdentity). Role sessions through a named trust are issued by roles.assume.

Temporary credentials

Every issuer returns the same shape: { token, tokenType: 'Bearer', format, expiresAt, expiresIn, audience?, session }. The token appears once, in the response body; these routes never set a cookie, so the credential only ever travels as Authorization: Bearer. session is an allowlist summary (id, tenant, kind, identity, expiry, MFA, and the role, trust, session name, and source identity when they apply) that never carries hashes, policies, or authority ids.

Opaque tokens are typed and checksummed: biam_sts_… for session tokens and biam_rol_… for role sessions, 58 characters, so secret scanners can find a leaked one. With format: 'jwt' the credential is instead a session JWT signed with the deployment's sts.jwt keys, which downstream services can verify offline against GET {basePath}/.well-known/jwks.json with createSessionTokenVerifier from better-iam/session-tokens. IAM itself still checks the stored session on every use, so revocation inside IAM is immediate; offline verifiers only see it when the token expires. Configure the ceilings, signing keys, and web-identity switches in temporary credentials.

A temporary credential never outlives its source, never passes recent-authentication checks, is never treated as an owner or root administrator, and cannot accept agreements, activate eligible bindings, or make other self-service changes. Policies can tell temporary credentials apart with principal.sessionKind and the session keys (principal.sessionName, principal.sessionTags.{key}, principal.webIdentitySubject, …) described in context keys.

Methods3
Serveriam.api.sts
Clientclient.sts
HTTPPOST /api/iam/sts/*
MethodWhat it doesAccess
assumeRoleWithWebIdentityExchanges a verified external OpenID Connect token, such as a GitHub Actions or Kubernetes token, for a role session under a web-identity trust.Public
getCallerIdentityReturns who the presented credential acts as: identity, tenant, session kind and id, format, MFA, expiry, and any role, trust, session name, tags, or web identity behind it.Credential
getSessionTokenMints a short-lived session token for your own identity from a signed-in session or an API key, optionally narrowed by a policy and attested with a fresh MFA code.Credential

assumeRoleWithWebIdentity

Exchanges a verified external OpenID Connect token, such as a GitHub Actions or Kubernetes token, for a role session under a web-identity trust.

POST/api/iam/sts/assumeRoleWithWebIdentity
client.sts.assumeRoleWithWebIdentity()Public

Used inTemporary access

  • Permission: None: public. The external token is the only credential, and the trust's claim conditions decide whether it is admitted.
  • Audited as: role:assumed-with-web-identity in the trust's tenant (outcome allow, or deny with a reason once the trust has resolved).
  • Errors: WEB_IDENTITY_REJECTED (403) for every refusal that depends on stored state or on the token (unknown or revoked trust, disabled provider, bad signature, issuer, audience or lifetime, unmet conditions, replay, inactive service account, and so on), always with the same body; FEATURE_DISABLED when sts.webIdentity.enabled is off, or for format: 'jwt' without sts.jwt; INVALID_INPUT for a malformed request, durationSeconds, or audience; RATE_LIMITED once the trust's exchange budget (sts.webIdentity.maxExchangesPerWindow) is spent; LIMIT_EXCEEDED (409) when the trust already holds sts.webIdentity.maxSessionsPerTrust live sessions; IP_BLOCKED or IP_NOT_ALLOWED from the tenant's network rules; ACCESS_DENIED for a JWT audience the service account may not obtain.

The session acts as the trust's service account in the trust's tenant, with the role's permissions bounded by the trust's ceiling, the optional scope-down policy, and the grant authorities of whoever created the trust and the provider. It is kind role with session.webIdentity set, never carries MFA, and ends when the provider is disabled, the trust is revoked, the service account is disabled, or either authority is revoked. sessionName is required and reaches policies as principal.sessionName; the verified subject reaches them as principal.webIdentitySubject.

Because callers cannot tell why a token was refused, administrators debug with trust.evaluateWebIdentity, which reports the reason and every failing condition. Each token can be redeemed once per provider unless the provider sets replayProtection: 'off'.

# In a GitHub Actions job with `permissions: id-token: write`.
ID_TOKEN=$(curl -sH "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
  "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://iam.example.com" | jq -r .value)
curl -s https://iam.example.com/api/iam/sts/assumeRoleWithWebIdentity \
  -H 'Content-Type: application/json' -H 'X-Better-IAM: 1' \
  -d "{\"tenantId\":\"$TENANT_ID\",\"trustId\":\"$TRUST_ID\",\"webIdentityToken\":\"$ID_TOKEN\",\"sessionName\":\"deploy-$GITHUB_RUN_ID\"}"
Input

A WebIdentityExchangeInput object:

Prop

Type

Returns

A WebIdentityCredential object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/sts/assumeRoleWithWebIdentity" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "trustId": "<trustId>",
  "webIdentityToken": "<webIdentityToken>",
  "sessionName": "<sessionName>"
}'
Signature
iam.api.sts.assumeRoleWithWebIdentity(
  input: WebIdentityExchangeInput,
): Promise<WebIdentityCredential>

getCallerIdentity

Returns who the presented credential acts as: identity, tenant, session kind and id, format, MFA, expiry, and any role, trust, session name, tags, or web identity behind it.

POST/api/iam/sts/getCallerIdentity
client.sts.getCallerIdentity()Credential

Used inTemporary access

  • Permission: None beyond a valid credential. It works for every session kind.
  • Audited as: nothing; it records no event.
  • Errors: UNAUTHENTICATED when the credential is invalid, expired, or revoked.

The credential is re-validated exactly as for any other call, so this doubles as the online revocation check for a service that holds a session JWT: a token that still verifies offline but was revoked in IAM fails here. The result is an allowlist projection and never includes hashes, policies, the source session, or authority ids. The CLI's whoami command prints it.

const caller = await iam.api.sts.getCallerIdentity({ token });
if (caller.sessionKind === 'role') console.log(`acting as role ${caller.roleId} via trust ${caller.trustId}`);
Returns

A CallerIdentity object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/sts/getCallerIdentity" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{}'
Signature
iam.api.sts.getCallerIdentity(
  credential: CredentialInput,
): Promise<CallerIdentity>

getSessionToken

Mints a short-lived session token for your own identity from a signed-in session or an API key, optionally narrowed by a policy and attested with a fresh MFA code.

POST/api/iam/sts/getSessionToken
client.sts.getSessionToken()Credential

Used inTemporary access

  • Permission: iam:session-tokens:create on your own identity (iam/{identityId}) in your tenant. Owners hold it through the Owner role; anyone else needs an explicit grant.
  • Audited as: iam:session-tokens:create and session-token:issued (with the new session id, duration, format, and whether an MFA code was verified).
  • Errors: CREDENTIAL_CHAINING_DISABLED (400) from a role session or another session token; IMPERSONATION_RESTRICTED from a "view as" session; MFA_NOT_ENROLLED for mfaCode from an API key or a person without an authenticator; INVALID_MFA for a wrong or reused code; RATE_LIMITED after too many codes; LIMIT_EXCEEDED (409) when you already hold sts.maxSessionTokensPerIdentity live tokens (50 by default); INVALID_INPUT for a duration, session name, format, or audience out of bounds; FEATURE_DISABLED for format: 'jwt' without sts.jwt; ACCESS_DENIED.

The token acts with your own grants, bounded by policy and by the source's own limits (an API key's scopes and authority carry over), and lasts durationSeconds: 3600 by default, at most sts.maxSessionTokenSeconds (12 hours unless configured), and never beyond the source's expiry. It ends when its source ends, and using it never refreshes the source's idle timer, so prefer an API key as the source for long-running automation.

With mfaCode (a current authenticator code; people only), the token carries mfa: true and a fresh MFA time, so it can then assume roles through trusts that require MFA. This is the MFA-then-assume pattern for command-line tools: roles cannot take a code themselves. Without a code, the source's MFA state is copied; API keys never carry MFA. The token's policy and its source's scopes decide which roles it may assume, but they do not carry into the role session, which acts with the role's permissions within the trust's ceiling and its own session policy.

// A CLI step-up: a one-hour, read-only token that can assume an MFA-gated role.
const { token, expiresAt } = await iam.api.sts.getSessionToken(
  { token: signedInToken },
  {
    mfaCode: '123456',
    sessionName: 'deploy-cli',
    policy: {
      version: 1,
      statements: [{ effect: 'allow', actions: ['iam:roles:assume', 'documents:read'], resources: ['*'] }],
    },
  },
);
Input

A GetSessionTokenInput object (the argument is optional):

Prop

Type

Returns

A TemporaryCredential object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/sts/getSessionToken" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{}'
Signature
iam.api.sts.getSessionToken(
  credential: CredentialInput,
  input?: GetSessionTokenInput | undefined,
): Promise<TemporaryCredential>

Was this page helpful?

Better IAM is created by Sean Filimon

Last updated

On this page