> This page is for version 0.10.
> For other versions, use one of these documentation indexes:
> - Latest (default): https://docs.nvidia.com/nemo-helix/latest/llms.txt
> - 0.10: https://docs.nvidia.com/nemo-helix/v0.10/llms.txt
> - 0.9: https://docs.nvidia.com/nemo-helix/v0.9/llms.txt
> - 0.8: https://docs.nvidia.com/nemo-helix/v0.8/llms.txt
> - 0.7: https://docs.nvidia.com/nemo-helix/v0.7/llms.txt
> - 0.6: https://docs.nvidia.com/nemo-helix/v0.6/llms.txt
> - 0.5: https://docs.nvidia.com/nemo-helix/v0.5/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/nemo-helix/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/nemo-helix/_mcp/server.

# OIDC Setup

Connect NeMo Helix to an external OIDC identity provider (IdP) so users sign in with your organization's identity and receive tokens for API access.

**Prerequisites**: You need an OAuth application registered in your IdP with the client ID, issuer URL, and device flow grant enabled. See the [Minimum IdP Checklist](#minimum-idp-checklist) below.

For a confidential client with Client Secret Basic, see [Confidential OIDC Client](/nemo-helix/v0.10/documentation/access-control/authentication/confidential-client). For login after setup, see [Using Authentication](/nemo-helix/v0.10/documentation/access-control/authentication/using-authentication). For the full config reference, see [Auth Configuration](/nemo-helix/v0.10/documentation/access-control/deployment).

## How OIDC Fits in NeMo Helix

When OIDC is enabled:

1. **Platform configuration** points to your IdP and defines independent public, confidential, and workload client profiles.
2. **Discovery**: Clients and the platform discover token and device-auth endpoints from the IdP's `.well-known/openid-configuration` (or from NeMo Helix's aggregated discovery at `{BASE_URL}/apis/auth/discovery`).
3. **Login**: Users obtain tokens via **device flow** (browser) or **password grant** (CI) using the CLI, or authorization code flow in Studio.
4. **API calls**: Clients send the access token in the `Authorization: Bearer <token>` header. NeMo Helix validates signed JWTs (signature, issuer, audience when configured, and expiry) and extracts the principal and configured groups for authorization.

OIDC provides the identity; workspace authorization comes from NeMo Helix role bindings. OAuth `scope` or `scp` values never become group principals or grant roles, but recognized NeMo Helix API scopes such as `platform:read` can narrow which APIs the bearer token can call.

## Flows Supported

* **Device authorization flow**: User runs `nemo auth login`; the CLI shows a code and opens the IdP page; after the user signs in and consents, the CLI receives an access token (and optionally a refresh token). Best for interactive use.
* **Resource owner password grant**: For non-interactive environments (CI), `nemo auth login --username <user> --password <pass>` exchanges credentials for a token. Your IdP must support this grant type.

> **Warning**
>
> Password grant sends user credentials directly to the IdP. It bypasses MFA and is disabled by many production IdPs. Use it only for CI/testing. Prefer device flow for interactive users.

### Public client session handling

Public clients use direct identity-provider sessions by default. The CLI obtains
and refreshes its provider tokens without routing them through NeMo Helix.

Set `public_client.server_side_sessions: true` when the public client should use
the NeMo Helix login broker instead. The client remains public and does not gain
a client secret; NeMo Helix completes Authorization Code + PKCE, stores the
provider refresh token in encrypted account credentials, and returns an opaque
refresh handle to the CLI.

```yaml
auth:
  oidc:
    public_client:
      client_id: "your-public-client-id"
      server_side_sessions: true
      authorization_endpoint: "https://your-idp.example.com/oauth2/authorize"
      token_endpoint: "https://your-idp.example.com/oauth2/token"
    server_sessions:
      encryption_key_env_var: NHX_AUTH_SESSION_ENCRYPTION_KEY
```

Create the session encryption key and inject it before setting
`server_side_sessions`. See
[Deployment Secrets](/nemo-helix/v0.10/documentation/access-control/deployment/deployment-secrets).
The environment variable must come from a deployment secret, not from the
platform ConfigMap. Studio does not use the login broker yet, so deployments
that serve Studio must leave this option disabled.

## Step-by-Step Configuration

### Step 1: Register an OAuth Application

In your IdP (Azure AD, Okta, Keycloak, etc.):

1. Create a new application registration
2. Note the **client ID**
3. Enable **device flow** (device authorization grant)
4. Add the OIDC scopes your login flow needs, such as `openid`, `profile`, `email`, and `offline_access`
5. If you want OIDC login tokens to carry NeMo Helix API-scope restrictions, expose and consent the NeMo Helix API scopes your deployment requests, such as `platform:read` and `platform:write`

### Step 2: Configure NeMo Helix

Set the OIDC settings in your platform config under `auth.oidc`:

```yaml
auth:
  enabled: true
  admin_email: "platform-admin@company.com"
  oidc:
    enabled: true
    issuer: "https://your-idp.example.com/realm"
    public_client:
      client_id: "your-public-client-id"
      # authorization_endpoint: "https://your-idp.example.com/oauth2/authorize"
      # token_endpoint: "https://your-idp.example.com/oauth2/token"
      # device_authorization_endpoint: "https://your-idp.example.com/oauth2/device"
      # Resource qualifier for IdPs that require api://<client-id>/platform:read
      # while default_scopes keeps the short name platform:read.
      # scope_prefix: "api://your-public-client-id/"
    # Optional: override claim names if your IdP uses non-standard names
    # email_claim: "upn"
    # subject_claim: "oid"
    # Groups are identity claims used for workspace role bindings.
    # groups_claim: "groups"
```

By default, NeMo Helix extracts group principals from the JWT `groups` claim. If your IdP sends group or entitlement values such as `employee`, `ml-team`, or `employee.profile.read` in a different claim, set `groups_claim` to that claim name, then grant roles to those exact principal strings. Values inside the OAuth `scope` or `scp` claim are not group principals and do not grant workspace roles.

### Step 3: Seed Auth Role Bindings

The `auth.admin_email` setting identifies the bootstrap admin, but the role binding is created by the platform seed task. Docker quickstart runs platform seed automatically, and Helm deployments run the platform-seed Job by default. For source installs, start services with `platform.seed_on_startup: true` or `NHX_SEED_ON_STARTUP=true`; if services are already running, run the seed task manually after services are healthy:

```bash
curl -sf "${NHX_BASE_URL:-http://localhost:8080}/health/ready"
uv run python -m nhx.platform_seed
```

The seed task creates the **HelixAdmin** binding for `auth.admin_email`, the wildcard **Editor** binding for the `default` workspace, the wildcard **Viewer** binding for the `system` workspace, and the wildcard **WorkspaceCreator** binding for `system` that preserves open workspace creation by default. Without this step, OIDC login can succeed while admin operations, workspace creation, and access to the default and system workspaces can still return `403 Forbidden`.

### Step 4: Configure Login Scopes (Optional)

Configure the OAuth scopes requested during CLI and Studio login:

```yaml
auth:
  oidc:
    public_client:
      client_id: "your-public-client-id"
      default_scopes: "platform:read platform:write openid profile email offline_access"
```

These are the scopes requested from the IdP during login. Most deployments request OIDC protocol scopes such as `openid`, `profile`, `email`, and `offline_access`. A deployment can also request NeMo Helix API scope names, such as `platform:read` and `platform:write`, when those scopes are exposed by the IdP application. If your IdP application does not expose NeMo Helix API scopes, omit them; RBAC still applies.

Keep the short NeMo Helix names in `default_scopes`. When the IdP expects `api://<client-id>/platform:read`, set `public_client.scope_prefix` to `api://<client-id>/`. The CLI and Studio add that prefix to scopes that contain `:` or end with `.default`, and send `openid`, `profile`, `email`, and `offline_access` unchanged. Discovery includes the prefix on the interactive client. The login prompt prints both forms, for example `platform:read (api://<client-id>/platform:read)`. Pass the short name to `nemo auth login --scope`. See [API Scopes](/nemo-helix/v0.10/documentation/access-control/authorization/api-scopes) for the scope value stored on the token.

### Step 5: Verify

After deploying the configuration:

```bash
# Log in via device flow
nemo auth login

# Check login status
nemo auth status
# Expected: shows principal email, scopes, token expiry

# Verify API access
nemo workspaces list
```

## OIDC Configuration Reference

| Field                 | Type   | Description                                                                                                                                                                                                  |
| --------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `enabled`             | bool   | Enable OIDC token validation.                                                                                                                                                                                |
| `issuer`              | string | IdP issuer URL (e.g., `https://login.microsoftonline.com/<tenant>/v2.0`).                                                                                                                                    |
| `additional_issuers`  | list   | Extra issuer URLs to accept (e.g., Azure AD v1 format).                                                                                                                                                      |
| `audience`            | string | Expected token audience. Unset by default; validation is skipped when unset.                                                                                                                                 |
| `email_claim`         | string | JWT claim for email (default: `email`).                                                                                                                                                                      |
| `subject_claim`       | string | JWT claim for subject (default: `sub`).                                                                                                                                                                      |
| `groups_claim`        | string | JWT claim for groups (default: `groups`).                                                                                                                                                                    |
| `jwks_uri`            | string | Override JWKS URI.                                                                                                                                                                                           |
| `public_client`       | object | Optional public browser/device client. Holds its client ID, external endpoints, scopes, scope prefix, device-flow options, and optional `server_side_sessions` opt-in.                                       |
| `confidential_client` | object | Optional server-side Authorization Code + PKCE client. Holds its client ID, secret environment-variable name, public authorization endpoint, internal token endpoint, registered Helix callback, and scopes. |
| `server_sessions`     | object | Shared server-side session encryption configuration. Required for a confidential client or a public client with `server_side_sessions: true`.                                                                |
| `workload`            | object | Optional workload exchange profile. Its presence enables workload identity and holds the workload client ID, token endpoint, audience, scopes, subject validation, and token-signing overrides.              |

### Breaking configuration migration

The client-profile model is a breaking change. Update server configuration and
bundled clients together. Removed flat fields fail validation; Helix does not
translate them at runtime.

| Removed `auth.oidc` field                                                                                                        | Replacement                                                  |
| -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| `client_id`                                                                                                                      | `public_client.client_id` or `confidential_client.client_id` |
| `client_authentication`                                                                                                          | Configure `public_client` or `confidential_client`           |
| `public_client_id`, `user_login_client_id`                                                                                       | `public_client.client_id`                                    |
| `client_secret_env_var`                                                                                                          | `confidential_client.client_secret_env_var`                  |
| `session_encryption_key_env_var`                                                                                                 | `server_sessions.encryption_key_env_var`                     |
| `login_redirect_uri`                                                                                                             | `confidential_client.login_redirect_uri`                     |
| `authorization_endpoint`                                                                                                         | The selected interactive profile's `authorization_endpoint`  |
| `token_endpoint`                                                                                                                 | The selected interactive profile's `token_endpoint`          |
| `device_authorization_endpoint`                                                                                                  | `public_client.device_authorization_endpoint`                |
| `bearer_token_source`, `default_scopes`, `scope_prefix`                                                                          | The corresponding field on each interactive profile          |
| `device_authorization_requires_device_id`, `device_authorization_display_name`, `device_token_request_includes_scope`            | The corresponding `public_client` field                      |
| `workload_token_exchange_enabled`                                                                                                | Configure `workload`; omit it to disable workload exchange   |
| `workload_client_id`                                                                                                             | `workload.client_id`                                         |
| `workload_token_endpoint`                                                                                                        | `workload.token_endpoint`                                    |
| `workload_audience`, `workload_scope`                                                                                            | `workload.audience`, `workload.scope`                        |
| `workload_token_issuer`, `workload_token_ttl_seconds`, `workload_token_key_id`, `workload_token_private_key_file`                | The corresponding `workload.token_*` field                   |
| `workload_allowed_audiences`, `workload_subject_jwks_uri`, `workload_subject_issuers`, `workload_subject_jwks_cache_ttl_seconds` | The corresponding `workload` subject-validation field        |
| `workload_kubernetes_token_review_enabled`                                                                                       | `workload.kubernetes_token_review_enabled`                   |

Auth discovery now exposes interactive clients only through structured
`oidc.clients` entries. Removed top-level client IDs, endpoints, scopes,
device-flow options, and `token_authorization_url` are not projected.

## Minimum IdP Checklist

Regardless of IdP, ensure:

1. You have registered an OAuth application and have the **client ID** (and client secret if required).
2. The application supports the **device authorization grant** (for `nemo auth login`).
3. The selected bearer token includes claims for email, subject, and groups (or you have configured `email_claim`, `subject_claim`, `groups_claim` to match your IdP's claim names).
4. Any group or entitlement values that should grant NeMo roles are present in the configured `groups_claim`.
5. If an interactive profile's `default_scopes` includes NeMo Helix API scopes, those scopes are exposed by the IdP application and consented according to the provider's model.

When `device_authorization_requires_device_id` is enabled, the CLI stores a random installation identifier at
`$XDG_STATE_HOME/nhx/oidc-device-id` (or `~/.local/state/nhx/oidc-device-id`) with mode `0600`. It reuses that value
for later logins. If `device_authorization_display_name` is unset, the CLI sends `NeMo Helix CLI`.
Set `device_token_request_includes_scope: false` when the provider accepts scopes only on the initial device
authorization request. The default remains enabled for providers that require the scope again while polling.

## Claim mapping

NeMo Helix maps JWT claims to [trusted identity headers](/nemo-helix/v0.10/documentation/access-control/security-model#trusted-identity-headers) using `email_claim`, `subject_claim`, and `groups_claim` (defaults: `email`, `sub`, `groups`). Override them when your IdP uses different claim names. Group values must match the principal strings you use for workspace members and role bindings.

| Purpose                | Config key      | Default  | Header                   |
| ---------------------- | --------------- | -------- | ------------------------ |
| Subject (Principal ID) | `subject_claim` | `sub`    | `X-NHX-Principal-Id`     |
| Email                  | `email_claim`   | `email`  | `X-NHX-Principal-Email`  |
| Groups                 | `groups_claim`  | `groups` | `X-NHX-Principal-Groups` |

> **Note**
>
> For ordinary OIDC bearer validation, group values come from the configured `groups_claim`. They are included in the `X-NHX-Principal-Groups` header and OPA policy data, where they can match explicit workspace member or role-binding principals. Group names may contain dots, for example `employee.profile.read`. OAuth scopes from `scope` or `scp` do not become group principals; recognized NeMo Helix API scopes in those claims can still restrict API access. See [Managing Access](/nemo-helix/v0.10/documentation/access-control/authorization/managing-access).

## Provider-Specific Notes

* **Azure AD**: See [Azure AD Setup](/nemo-helix/v0.10/documentation/access-control/authentication/providers/azure-ad-entra-id) (issuer, `additional_issuers`, device flow, claim overrides).
* **Okta**: Choose the correct authorization server (custom vs org). Ensure device flow is enabled.
* **Keycloak**: Configure realm, client, and role mappers. Enable device flow in client settings.
* **Other providers**: See [Generic OIDC Provider](/nemo-helix/v0.10/documentation/access-control/authentication/providers/generic-oidc) for a provider-agnostic checklist.

## Related

* [Using Authentication](/nemo-helix/v0.10/documentation/access-control/authentication/using-authentication) — Log in, make API calls, and manage tokens.
* [Auth Configuration](/nemo-helix/v0.10/documentation/access-control/deployment) — Full auth config reference.