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

# Azure OIDC Integration

> Access Azure resources from Ona environments using workload identity federation with Microsoft Entra ID.

<Note>Available on the Enterprise plan. [Contact sales](https://ona.com/contact/sales) to learn more.</Note>

Ona environments can access Azure resources using [workload identity federation](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation). You configure a federated credential on an Azure app registration or user-assigned managed identity that trusts Ona's OIDC tokens. Environments exchange their JWT for an Azure access token without storing any secrets.

## Azure-specific constraints

Azure Entra ID federated credentials have two limits that shape how you design the trust:

* **Exact match on `subject`** — wildcards are not supported. You need one federated credential per distinct `sub` value you want to authorize.
* **Maximum 20 federated credentials** per app registration or managed identity.

Reference: [Microsoft — Federated identity credential considerations](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-considerations).

Plan the [sub claim composition](#sub-claim-strategy) before creating credentials so the number of subs you need stays comfortably under 20.

## Prerequisites

* V3 tokens enabled on the [OIDC Token Configuration](https://app.gitpod.io/settings/security/oidc) page. See [Enable V3 tokens](/ona/configuration/oidc#enable-v3-tokens).
* Azure CLI installed in your environment.
* An Azure app registration or user-assigned managed identity. The examples below use an app registration; the same federated-credential and CLI flow works for managed identities.

## How it works

1. Ona issues a JWT signed with RS256, containing identity claims about the environment, user, or service account.
2. Azure validates the token against Ona's [OIDC discovery endpoint](https://app.gitpod.io/.well-known/openid-configuration) and [JWKS](https://app.gitpod.io/.well-known/jwks.json).
3. If `iss`, `aud`, and `sub` match the federated credential, Azure issues a service principal access token.
4. The Azure CLI or any Azure SDK uses that access token to call Azure APIs.

## Step 1: Create an app registration or managed identity

Use either an [app registration](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app) or a [user-assigned managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-manage-user-assigned-managed-identities). Note the **client ID** and **tenant ID** — both are required for the login flow.

App registration:

```bash theme={null}
az ad app create --display-name ona-environment
# Note the appId returned — this is your --client-id.
```

User-assigned managed identity:

```bash theme={null}
az identity create \
  --name ona-environment \
  --resource-group <RESOURCE_GROUP> \
  --location <LOCATION>
```

## Step 2: Inspect your Ona token

Run this in an Ona environment to see what `sub` value Azure will need to match:

```bash theme={null}
ona idp token --audience api://AzureADTokenExchange --decode
```

Record the `sub` value verbatim — Azure requires an exact-match string.

## Step 3: Add a federated identity credential

Add a [federated identity credential](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-create-trust) that trusts Ona's OIDC tokens for the recorded `sub`.

For an app registration:

```bash theme={null}
az ad app federated-credential create \
  --id <CLIENT_ID> \
  --parameters '{
    "name": "ona-project-access",
    "issuer": "https://app.gitpod.io",
    "subject": "organization_id:<ORG_ID>:project_id:<PROJECT_ID>",
    "audiences": ["api://AzureADTokenExchange"]
  }'
```

For a user-assigned managed identity:

```bash theme={null}
az identity federated-credential create \
  --name ona-project-access \
  --identity-name ona-environment \
  --resource-group <RESOURCE_GROUP> \
  --issuer https://app.gitpod.io \
  --subject "organization_id:<ORG_ID>:project_id:<PROJECT_ID>" \
  --audiences api://AzureADTokenExchange
```

| Field       | Value                                     |
| ----------- | ----------------------------------------- |
| `issuer`    | `https://app.gitpod.io`                   |
| `subject`   | The exact `sub` value from your Ona token |
| `audiences` | `api://AzureADTokenExchange`              |

<Warning>The `subject` must match the `sub` claim **exactly**, including case and order of key/value pairs. New federated credentials take a few minutes to propagate; token exchange may fail with `AADSTS70021` during this window.</Warning>

## Step 4: Assign Azure RBAC roles

Grant the app registration or managed identity access to the Azure resources it needs:

```bash theme={null}
az role assignment create \
  --assignee <CLIENT_ID> \
  --role "Key Vault Secrets User" \
  --scope /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP>/providers/Microsoft.KeyVault/vaults/<VAULT_NAME>
```

## Step 5: Authenticate from an environment

Use `ona idp login azure` to exchange the OIDC token and configure the Azure CLI:

```bash theme={null}
ona idp login azure \
  --client-id <CLIENT_ID> \
  --tenant-id <TENANT_ID>
```

To set an active subscription as part of the login:

```bash theme={null}
ona idp login azure \
  --client-id <CLIENT_ID> \
  --tenant-id <TENANT_ID> \
  --subscription-id <SUBSCRIPTION_ID>
```

Subsequent `az` calls use the federated credentials:

```bash theme={null}
az keyvault secret show --vault-name my-vault --name my-secret
```

### Flags

| Flag                | Required | Env var                     | Notes                                               |
| ------------------- | -------- | --------------------------- | --------------------------------------------------- |
| `--client-id`       | yes      | `IDP_AZURE_CLIENT_ID`       | App registration or managed identity client ID      |
| `--tenant-id`       | yes      | `IDP_AZURE_TENANT_ID`       | Directory (tenant) ID                               |
| `--subscription-id` | no       | `IDP_AZURE_SUBSCRIPTION_ID` | If set, becomes the active subscription after login |
| `--audience`        | no       | —                           | Defaults to `api://AzureADTokenExchange`            |

### Automate on environment startup

Add the login to your [automations](/ona/configuration/tasks-and-services/overview):

```yaml theme={null}
# automations.yaml
tasks:
  azure-login:
    name: Azure Login
    command: ona idp login azure --client-id <CLIENT_ID> --tenant-id <TENANT_ID>
    triggeredBy:
      - postDevcontainerStart
```

### Manual exchange (without `ona idp login azure`)

Use this when integrating with non-CLI tooling or other Azure SDKs:

```bash theme={null}
TOKEN=$(ona idp token --audience api://AzureADTokenExchange)

az login --service-principal \
  --username <CLIENT_ID> \
  --tenant <TENANT_ID> \
  --federated-token "$TOKEN"
```

Or call Microsoft's token endpoint directly:

```bash theme={null}
TOKEN=$(ona idp token --audience api://AzureADTokenExchange)

curl -X POST "https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token" \
  -d "client_id=<CLIENT_ID>" \
  -d "scope=https://management.azure.com/.default" \
  -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  -d "client_assertion=$TOKEN" \
  -d "grant_type=client_credentials"
```

For programmatic access in Python, the Azure Identity SDK accepts a token-fetching callable via [`ClientAssertionCredential`](https://learn.microsoft.com/en-us/python/api/azure-identity/azure.identity.clientassertioncredential):

```python theme={null}
import subprocess
from azure.identity import ClientAssertionCredential

def get_ona_token() -> str:
    return subprocess.run(
        ["ona", "idp", "token", "--audience", "api://AzureADTokenExchange"],
        capture_output=True, text=True, check=True,
    ).stdout.strip()

credential = ClientAssertionCredential(
    tenant_id="<TENANT_ID>",
    client_id="<CLIENT_ID>",
    func=get_ona_token,
)
```

## Sub claim strategy

Azure's exact-match constraint means you should pick the coarsest `sub` that still expresses your trust boundary. Each unique `sub` you want to authorize costs one of your 20 federated credentials per identity.

The default V3 environment `sub` depends on whether the environment belongs to a project:

* **With project:** `organization_id:<orgID>:project_id:<projID>` — stable across all environments in the project.
* **Without project:** `organization_id:<orgID>` — matches every environment in the org.

Pick one of the patterns below.

### Per-project (recommended)

`sub` = `organization_id` + `project_id` (default — no configuration needed).

```
organization_id:<orgID>:project_id:<projID>
```

| Federated credentials | Use case                                                                                                      |
| --------------------- | ------------------------------------------------------------------------------------------------------------- |
| 1 per project         | All users in a project assume the same Azure role. Simplest setup, stays well within the 20-credential limit. |

### Per-user, per-project

Add `creator_email` (or `creator_id`) to the extra sub fields on the [OIDC Token Configuration](https://app.gitpod.io/settings/security/oidc) page. The `sub` becomes:

```
organization_id:<orgID>:project_id:<projID>:creator_email:user@example.com
```

| Federated credentials | Use case                                                                                                |
| --------------------- | ------------------------------------------------------------------------------------------------------- |
| 1 per user × project  | Different users get different Azure permissions within the same project. Watch the 20-credential limit. |

### Per-user, all projects

Add `user_id` (for user tokens) or `creator_id` (for environment tokens) to the extra sub fields.

```
organization_id:<orgID>:user_id:<userID>
```

| Federated credentials | Use case                                              |
| --------------------- | ----------------------------------------------------- |
| 1 per user            | User-level Azure role assumption across all projects. |

### Per-repository

Add `environment_initializers.git.remote_uri` to the extra sub fields. Useful when you want the trust to follow a repository regardless of which Ona project wraps it.

```
organization_id:<orgID>:project_id:<projID>:environment_initializers.git.remote_uri:https%3A//github.com/org/repo.git
```

<Note>Colons in values are URL-encoded as `%3A` in the sub claim.</Note>

See [Customizing the sub claim](/ona/configuration/oidc#customizing-the-sub-claim) for the full list of fields you can add.

### Limits

* Maximum 20 federated credentials per app registration or managed identity. If you need more, create additional identities with different role assignments.
* The `subject` field has a 600-character limit. Long sub compositions (many extra fields with UUIDs) can approach this.

## Token claims available to your application

Even though Azure only matches against `sub`, the rest of the JWT body is available to your application after login. Use top-level claims for downstream authorization (audit logs, tenant routing, group-based checks).

| Claim                      | Description                                                 |
| -------------------------- | ----------------------------------------------------------- |
| `organization_id`          | Ona organization UUID                                       |
| `project_id`               | Ona project UUID, when the environment is project-scoped    |
| `environment_id`           | Environment UUID (changes per environment)                  |
| `runner_id`                | Runner UUID                                                 |
| `creator_id`               | Ona user UUID of the creator                                |
| `creator_principal`        | `user` or `service_account`                                 |
| `creator_email`            | Email of the user who created the environment               |
| `creator_name`             | Display name of the creator                                 |
| `creator_idp`              | URL of the creator's identity provider                      |
| `creator_idp_claims`       | Non-sensitive claims forwarded from the SSO provider        |
| `environment_initializers` | Git remote and context URL the environment was created from |

See [Token structure](/ona/configuration/oidc#token-structure) for the full schema and per-principal variants.

### SSO claims forwarding

`creator_idp_claims` forwards string-valued claims from your SSO provider (for example, Entra ID `groups` or `preferred_username`). To enable additional claims:

1. Open **Organization Settings → Login and Security** in Ona.
2. Add the scopes your IdP returns on login (for example, `profile`, `email`, `groups`).
3. Configure your IdP to release those claims to Ona.

You can include any string-valued SSO claim in the `sub` using the `creator_idp_claims.<key>` syntax.

### A note on the `creator_` prefix

In Ona, environments have their own identity — they are machines, not users. The `creator_` prefix makes explicit that those claims describe the user who created the environment, not the environment itself. The distinction matters when environments are launched by automations or shared across users.

## V2 tokens

V2 tokens also work with Azure federated credentials. The V2 `sub` uses a path-based format (for example, `org:<orgID>/prj:<projectID>/env:<envID>`). Set the federated credential's `subject` to the exact V2 `sub` value.

V2 tokens carry fewer claims (`org`, `gsub`, and standard JWT fields), so trust is limited to matching `sub`. New integrations should use V3. See [V2 tokens](/ona/configuration/oidc#v2-tokens) for the full V2 reference.

## Troubleshooting

**`AADSTS70021: No matching federated identity record found`**

* The `sub` claim in your Ona token does not match any federated credential's `subject`.
* Decode your token: `ona idp token --audience api://AzureADTokenExchange --decode`.
* Compare it to the `subject` you registered. The match must be exact, including case.
* Newly created federated credentials take a few minutes to propagate. Retry after the wait.

**`AADSTS700024: Client assertion is not within its valid time range`**

* The token has expired. Ona OIDC tokens are short-lived. Re-run `ona idp login azure`.

**`AADSTS700016: Application not found in the directory`**

* `--client-id` must be the **client ID** (`appId`) of the app registration or managed identity, not its object ID.

**`missing --client-id` / `missing --tenant-id`**

* Pass the flags or set `IDP_AZURE_CLIENT_ID` / `IDP_AZURE_TENANT_ID` in the environment.

## Further reading

* [Azure: Workload identity federation](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation)
* [Azure: Federated identity credential considerations](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-considerations)
* [Azure: Configure federated credentials on a managed identity](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-create-trust-user-assigned-managed-identity)
* [Azure: ClientAssertionCredential (Python)](https://learn.microsoft.com/en-us/python/api/azure-identity/azure.identity.clientassertioncredential)
* [Ona OIDC overview](/ona/configuration/oidc)
