# sts (/docs/reference/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`](/docs/reference/api/roles#assume).

## Temporary credentials [#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](/docs/operations/deployment/configuration#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](/docs/guides/authorization/conditions#context-keys).

| Method                                                    | What it does                                                                                                                                                                  | Access     |
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| [`assumeRoleWithWebIdentity`](#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.                              | Public     |
| [`getCallerIdentity`](#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. | Credential |
| [`getSessionToken`](#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.           | Credential |

## assumeRoleWithWebIdentity [#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.

**HTTP:** `POST /api/iam/sts/assumeRoleWithWebIdentity` (no credential) · **Browser client:** `client.sts.assumeRoleWithWebIdentity()`

* **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`](/docs/reference/api/trust#evaluatewebidentity), which reports the reason and every
failing condition. Each token can be redeemed once per provider unless the provider sets `replayProtection: 'off'`.

```bash
# 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\"}"
```

```ts title="Signature"
iam.api.sts.assumeRoleWithWebIdentity(
  input: WebIdentityExchangeInput,
): Promise<WebIdentityCredential>
```

## getCallerIdentity [#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.

**HTTP:** `POST /api/iam/sts/getCallerIdentity` (requires a credential) · **Browser client:** `client.sts.getCallerIdentity()`

* **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.

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

```ts title="Signature"
iam.api.sts.getCallerIdentity(
  credential: CredentialInput,
): Promise<CallerIdentity>
```

## getSessionToken [#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.

**HTTP:** `POST /api/iam/sts/getSessionToken` (requires a credential) · **Browser client:** `client.sts.getSessionToken()`

* **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`.

```ts
// 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: ['*'] }],
    },
  },
);
```

```ts title="Signature"
iam.api.sts.getSessionToken(
  credential: CredentialInput,
  input?: GetSessionTokenInput | undefined,
): Promise<TemporaryCredential>
```
