Skip to content

Connect an identity provider to AI Workspace

AI Workspace delegates user login to any identity provider (IdP) that speaks OpenID Connect (OIDC). This guide is written for the administrator who deploys AI Workspace, and it covers the configuration every IdP needs. For an example of these steps applied to one specific IdP, see Set up Asgardeo as your identity provider.

How OIDC login works here

AI Workspace is served by a Backend-for-Frontend (BFF), and the BFF is the OIDC client — not the browser. It runs the whole authorization code exchange server-side, keeps the resulting tokens in a server-side session, and gives the browser only an HttpOnly cookie. Register AI Workspace as a confidential client, not a public or single-page application client.

Two services need configuring, and they must agree:

  • AI Workspace sends users to the IdP and reads identity claims out of the returned token.
  • Platform API verifies every token against the IdP's JWKS endpoint and authorizes each request from the token's privileges.

Both read the same configs/config.toml — AI Workspace from the [ai_workspace.*] tables, the Platform API from the [platform_api.*] tables.

What your identity provider must support

Check your IdP against these requirements before you start:

Requirement Details
OIDC discovery Serves /.well-known/openid-configuration at its authority URL. AI Workspace discovers every endpoint from there, so you configure the authority and nothing else.
JWT access tokens Access tokens are signed JWTs. The Platform API reads claims out of the token directly, so opaque tokens don't work.
JWKS endpoint Publishes the signing keys the Platform API verifies token signatures against.
Confidential client Issues a client secret.
Authorization code and refresh token grants Both enabled on the application.
Custom claims Emits organization identity in the token. Claim names are configurable, the claims themselves are required.

Step 1: Register AI Workspace as a confidential client

In your IdP, create a confidential OIDC application with these settings. Replace <your-domain> with the address users reach AI Workspace at, including the port when it isn't 443 — localhost:9643 for the Docker Compose quickstart.

  1. Set the authorized redirect URL to https://<your-domain>/api/auth/callback. This is the BFF's own server-side callback route, not a page in the app.
  2. Set the post-logout redirect URL to https://<your-domain>/login.
  3. Enable the Authorization Code and Refresh Token grants.
  4. Record the client ID and client secret.

Step 2: Emit the claims AI Workspace expects

Both services read user and organization identity out of the access token by claim name. The defaults are listed here; you can either configure your IdP to emit these names, or keep your IdP's names and map them in Step 4.

Default claim Carries
sub The user's unique ID
username The username shown in the workspace
email The user's email address
organization Organization ID
org_name Organization display name
org_handle Organization URL slug
scope Space-separated scope string
roles The user's roles

The organization claims matter beyond display: AI Workspace creates an organization on a user's first login from them, and assigns it the region set in [ai_workspace] default_org_region.

Step 3: Choose how privileges reach the token

The Platform API authorizes each request in one of two modes, set in [platform_api.auth.authorization]. Pick the one that matches what your IdP can put in a token.

Scope mode (mode = "scope", the default) reads the scope claim and checks it against the scope each endpoint requires. Your IdP has to issue tokens carrying the platform's ap:* scopes, which usually means registering those scopes in the IdP and granting them to the application.

Role mode (mode = "role") reads the roles claim and expands each role into scopes through a YAML mapping file. Your IdP only has to emit role names, which most products do out of the box. Set role_to_scope_mapping to the path of that file — the packs mount an editable copy at /etc/platform-api/role-to-scope-mapping.yaml. It ships these roles:

Role Grants
ap_admin Full access to every resource and operation
ap_operator Gateway and deployment operations
ap_publisher Creating and publishing APIs and proxies
ap_subscriber Applications and subscriptions
ap_viewer Read-only access

Edit that file to change what a role grants, or to add your own. It's mounted configuration rather than part of the image, and the Platform API reads it at startup, so a change needs a restart.

Role mode is the lighter integration of the two. Map your IdP's groups onto these role names, and you never register a scope in the IdP at all.

Step 4: Configure the Platform API

Set the Platform API to verify tokens against your IdP. mode selects exactly one authentication mode, so setting it to "idp" also stops the file-based login endpoint from being used.

[platform_api.auth]
mode = "idp"

[platform_api.auth.idp]
name     = "my-idp"                                    # friendly name, used in logs
jwks_url = "https://idp.example.com/oauth2/jwks"
issuer   = ["https://idp.example.com/oauth2/token"]    # accepted "iss" values
audience = ["<ai-workspace-client-id>"]                # accepted "aud" values; empty skips the check

[platform_api.auth.authorization]
enabled = true
mode    = "role"                                                    # or "scope"
role_to_scope_mapping = "/etc/platform-api/role-to-scope-mapping.yaml"

issuer takes a list, so a provider that mints tokens under more than one issuer URL is accommodated by naming each. Set audience to your client ID rather than leaving it empty, so a token minted for a different application is rejected.

If your IdP's claim names differ from the defaults in Step 2, override them:

[platform_api.auth.claim_mappings]
organization = "org_id"
org_name     = "org_name"
org_handle   = "org_handle"
user_id      = "sub"
username     = "username"
email        = "email"
scope        = "scope"
roles        = "roles"

Each value is either a flat top-level claim name or a dot-separated path into a nested claim. That path syntax is what accommodates providers that nest their claims — Keycloak puts roles under realm_access.roles, for example.

Step 5: Configure AI Workspace

In the same file, point AI Workspace at the IdP:

[ai_workspace]
domain             = "<your-domain>"
default_org_region = "us"

[ai_workspace.auth]
mode = "oidc"

[ai_workspace.auth.oidc]
authority                = "https://idp.example.com/oauth2/token"
client_id                = "<ai-workspace-client-id>"
client_secret            = '{{ env "APIP_AIW_AUTH_OIDC_CLIENT_SECRET" }}'
redirect_url             = "https://<your-domain>/api/auth/callback"
post_logout_redirect_url = "https://<your-domain>/login"

# A sibling of [ai_workspace.auth.oidc], not nested in it — applies to both auth modes.
[ai_workspace.auth.claim_mappings]
organization = "org_id"        # claim carrying the org ID
org_name     = "org_name"      # org display name
org_handle   = "org_handle"    # org URL slug
username     = "username"
email        = "email"
scope        = "scope"         # space-separated scope string
roles        = "roles"

Three things to get right:

  • authority is the issuer URL. Endpoints are discovered from it, so it must be the URL whose /.well-known/openid-configuration describes your IdP.
  • redirect_url must match the URL registered in Step 1 exactly, character for character.
  • [ai_workspace.auth.claim_mappings] must give every key it shares with [platform_api.auth.claim_mappings] the same value. Both services read the same token, so a mismatch means one of them reads the wrong claim. AI Workspace uses username and email to render the signed-in user, so they matter here as much as the organization claims. The table has no user_id key — only the Platform API maps that claim.

Every [ai_workspace.auth.oidc] key except scope defaults to empty. In OIDC mode the server refuses to start until each one is set, so a misconfiguration fails at startup rather than at a user's first login.

Leave scope unset to request the full ap:* scope set the Platform API authorizes against, which is the recommended starting point. If you trim it, keep offline_access, or token refresh stops working.

Supply the client secret

Never write the client secret as a literal in config.toml. The token above reads it from an environment variable instead:

APIP_AIW_AUTH_OIDC_CLIENT_SECRET=<ai-workspace-client-secret>

In the Docker Compose distribution, set it in the git-ignored api-platform.env, which is the file Compose loads into both containers. In production, prefer a mounted secret file. Swap the token for '{{ file "/secrets/ai-workspace/oidc_client_secret" }}', then mount the secret at that path. Both forms fail closed: a missing variable or unreadable file aborts startup rather than falling back to an empty credential. See Sensitive values in config.toml.

Step 6: Restart and verify

Restart both services so they reload the configuration:

docker compose up -d --force-recreate

Open AI Workspace. Instead of the username and password form, you're redirected to your IdP's hosted login page, and land back in the workspace after signing in.

If sign-in fails, the mismatch is usually in one of these:

  • The redirect_url in config.toml differs from the one registered in the IdP.
  • The IdP issues opaque access tokens rather than JWTs.
  • issuer doesn't match the token's iss claim, or audience doesn't match its aud claim.
  • The token carries organization claims under names the claim_mappings tables don't name.

Set [ai_workspace.logging] level = "debug" and [platform_api.logging] level = "debug" to see which claim or check fails.

Claim names for common identity providers

The roles claim is the one that most often differs. These paths are known to work:

Identity provider claim_mappings roles value
Asgardeo roles
Microsoft Entra ID roles
Keycloak realm_access.roles, or resource_access.<client>.roles

Next steps