> ## Documentation Index
> Fetch the complete documentation index at: https://docs.basaltic.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# OIDC federation

> Exchange workload identities for temporary credentials without distributing long-lived access keys.

Use OIDC when a workload already has an identity and needs temporary credentials
in another system. The identity issuer signs a short-lived JWT. The destination
checks its signature, issuer, audience, and subject before issuing its own
credentials.

| Workload | Identity proof | Exchange |
| - | - | - |
| GitHub Actions → Basaltic | GitHub job OIDC JWT | [GitHub Actions](https://docs.basaltic.sh/iam/github-actions.md) |
| Basaltic API session → external platform | `GetWebIdentityToken` with the destination audience | [Token API](#generate-a-token-through-the-api) |
| External workload → Basaltic | JWT from an account-registered OIDC issuer | Basaltic `AssumeRoleWithWebIdentity` |
| AWS workload → Basaltic | JWT from AWS STS `GetWebIdentityToken` | [AWS to Basaltic](https://docs.basaltic.sh/iam/aws-federation.md#aws-to-basaltic) |
| Basaltic VM → AWS | VM identity JWT from the metadata service | [Basaltic to AWS](https://docs.basaltic.sh/iam/aws-federation.md#basaltic-to-aws) |
| Basaltic VM → another OIDC relying party | The same VM identity endpoint, with that party's audience | [VM identity tokens](https://docs.basaltic.sh/iam/vm-identity.md) |

An identity proof and an API access token serve different purposes. An OIDC JWT
proves identity during an exchange. The resulting credentials authorize API
requests according to the destination role's permissions. Basaltic API bearer
tokens use a separate signing key and cannot substitute for VM identity tokens.

## Generate a token through the API

An authenticated service account or assumed-role session can call `GetWebIdentityToken`. Human users first assume an account role. The selected account must match the authenticated workload. VM metadata remains available when you want the VM to obtain its proof directly.

Grant `iam:GetWebIdentityToken` on the caller’s own role or service-account CRN. Restrict `basalt:IdentityTokenAudience` to the destination. For example:

```json theme={null}
{
  "version": "2024-01-01",
  "statements": [{
    "effect": "allow",
    "actions": ["iam:GetWebIdentityToken"],
    "resources": ["crn:iam::production:service-account/deploy-bot"],
    "conditions": [{
      "operator": "equals",
      "key": "basalt:IdentityTokenAudience",
      "values": ["https://deploy.example.com"]
    }]
  }]
}
```

Use a valid Basaltic API bearer token in `BASALTIC_TOKEN`. Keep the response private:

```bash theme={null}
umask 077
curl --fail --silent --show-error \
  -H "Authorization: Bearer $BASALTIC_TOKEN" \
  -H 'X-Account-Id: production' \
  -H 'Content-Type: application/json' \
  -d '{"audience":"https://deploy.example.com","duration_seconds":300}' \
  https://iam.basaltic.sh/v1/get-web-identity-token > identity-response.json
jq -r .token identity-response.json > identity.jwt
rm -f identity-response.json
```

The response contains `token` and `expires_at`. The lifetime defaults to 300 seconds; explicit values range from 60 to 300. The source session must remain valid for that lifetime. `basalt:IdentityTokenDurationSeconds` can restrict it further. Renew by calling the endpoint again.

The subject is derived from the authenticated identity. Names, caller-selected subjects, and arbitrary JWT claims are not accepted:

```text theme={null}
org:ORGANIZATION_UUID:account:ACCOUNT_UUID:role:ROLE_UUID
org:ORGANIZATION_UUID:account:ACCOUNT_UUID:service-account:SERVICE_ACCOUNT_UUID
org:ORGANIZATION_UUID:account:ACCOUNT_UUID:instance:INSTANCE_UUID
```

A direct VM metadata session retains the VM subject. A chained session represents its target role. Revoking the source session or removing its permission blocks new API issuance. Detaching a VM role blocks metadata issuance; also revoke any existing Basaltic sessions to stop API issuance. Already issued proofs remain valid until expiration.

## Choose the destination audience

The `audience` is chosen for the receiving platform, not fixed to AWS. Use the value that platform expects and configure matching issuance permissions and destination trust. AWS examples use `sts.amazonaws.com`; another relying party might require a URL, client identifier, or URN. The audience `basaltic` is reserved for Basaltic API tokens.

The JWT claim is `aud`. The spelling `iam.basaltic.sh:aud` is an AWS trust-policy condition key referring to that claim. Other platforms use their own claim-mapping configuration. They must support external workload JWTs and Basaltic’s ES256 signatures; an interactive OIDC login integration alone is not sufficient.

## Accepting an external identity

Three independent checks must pass:

1. Register the issuer in the **same account as the target role**, with the
   audiences that account accepts.
2. Add that provider's CRN to the role's trust policy and restrict the subject.
3. Attach permission policies to the role for the operations the workload needs.

Registration grants no permissions. Trust permits a workload to assume the role;
permission policies determine what its resulting session can do.

See [registering a provider](https://docs.basaltic.sh/iam/roles.md#trusting-an-oidc-provider) for Console and
API setup. The issuer URL must use HTTPS. Its discovery document and JWKS must
be reachable over the public internet. Discovery must name the same issuer and
advertise supported signing algorithms. The issuer and provider name are
immutable; audiences, description, and tags can be updated.

Basaltic validates the JWT signature and expiration and requires at least one
of its audiences to match the provider's current audience list. An issuer
registered in another account does not establish trust in your account.

## Restricting trust

Always bind trust to the intended subject. Use an audience condition too when a
provider accepts multiple audiences but a role should accept only one.

```json theme={null}
{
  "principals": ["crn:iam::production:oidc-provider/workload-issuer"],
  "conditions": [
    {
      "operator": "equals",
      "key": "basalt:webidentity:Subject",
      "values": ["YOUR_EXACT_WORKLOAD_SUBJECT"]
    },
    {
      "operator": "equals",
      "set_operator": "for_any_value",
      "key": "basalt:webidentity:Audience",
      "values": ["urn:example:basaltic:production"]
    }
  ]
}
```

`Subject` is a string. `Audience` is a set, even when a JWT contains only one
audience: its condition needs `set_operator: "for_any_value"` for a membership
check. A scalar `equals` condition on `Audience` does not match that set.

Generic customer providers expose the verified issuer, subject, and audience to
trust evaluation. Arbitrary custom JWT claims are not available as policy keys.
Do not assume an AWS EC2 instance claim, a CI job label, or another custom field
restricts trust unless the supported subject itself provides that restriction.

For GitHub Actions, use the exact subject issued to your workflow, including
immutable owner and repository IDs when present. Restrict the organization or
repository and the intended branch or environment. GitHub providers require a
positive organization or repository subject restriction. A session name is an
audit label; it cannot change the verified subject.

## Exchanging the proof

Send the JWT and full target role CRN to the public IAM endpoint. This exchange
uses the JWT itself and needs no existing Basaltic access key or bearer token.

```http theme={null}
POST https://iam.basaltic.sh/v1/assume-role-with-web-identity
Content-Type: application/json

{
  "role": "crn:iam::production:role/deploy",
  "web_identity_token": "SIGNED_OIDC_JWT",
  "session_name": "deploy-main",
  "duration_seconds": 900
}
```

The full role CRN selects the account. There is no `account` field in the request.
The response contains temporary access-key credentials and a bearer access token.
Use the bearer token for Basaltic APIs and the temporary access-key credentials
for the S3-compatible endpoint. See [authentication](https://docs.basaltic.sh/authentication.md).

Basaltic role sessions default to one hour. An explicit duration must be at
least 900 seconds and within the role's configured maximum, up to 12 hours.
The proof's expiration controls when it may be exchanged; it does not shorten a
session already issued from it.

## Refresh and revocation

Obtain a fresh identity proof from the issuer and exchange it before the
Basaltic session expires. There is no OIDC refresh token in this exchange.

Updating a provider's audiences or deleting it prevents new exchanges that no
longer meet the registration. Changing role trust affects new exchanges.
Existing Basaltic sessions remain valid until they expire or are revoked.
[Revoke a session](https://docs.basaltic.sh/iam/roles.md) to end its bearer and temporary credentials.
Recreating a provider with the same name does not restore its old trust binding;
update the role's trust policy to authorize the new provider.

Providers marked `is_system` are platform-managed and cannot be changed or
deleted. Customer workloads should register their own publicly reachable issuer.

## Discovery and keys

Basaltic publishes two metadata documents with different purposes:

| URL | Purpose |
| - | - |
| `https://iam.basaltic.sh/.well-known/openid-configuration` | External workload identity federation |
| `https://iam.basaltic.sh/.well-known/workload-jwks.json` | Public verification keys for workload identity JWTs |
| `https://iam.basaltic.sh/.well-known/oauth-authorization-server` | Basaltic API OAuth authorization-server metadata |
| `https://iam.basaltic.sh/.well-known/jwks.json` | Public keys for Basaltic API tokens |

Discover and cache the appropriate JWKS; do not pin a single signing key.
During key rotation, multiple public keys can be published. Verify the JWT's
`kid`, algorithm, issuer, audience, subject, and expiration.

## Troubleshooting

| Result | Check |
| - | - |
| Exchange returns `401` | Issuer registration in the target account, signature, expiration, allowed audience, discovery and JWKS reachability |
| Exchange returns `403` | Role trust, exact subject, audience set operator, and account or organization state |
| Exchange succeeds but an API request returns `403` | Role permissions, boundaries, and the resource CRN being authorized |
| A role session keeps working after provider deletion | Revoke the existing session; deleting the provider blocks new exchanges |
| A recreated workload cannot assume the old role | Compare its immutable subject and explicitly update trust if the replacement should have access |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.