Identity & Access Management (OIDC)¶
Decision Control delegates user authentication to an OpenID Connect (OIDC) provider via the shared aletyx-oidc-spring-boot-starter. Authorization is driven by app roles carried in the JWT issued by the provider. Two providers are supported out of the box:
- Microsoft Entra ID (formerly Azure AD)
- Keycloak
Provider selection is controlled by ALETYX_OIDC_PROVIDER; defaults to entra. Setting it to none disables authentication entirely and is intended for local diagnostics only — never run a production deployment with none.
Required claims, scopes, and roles¶
Regardless of provider, Decision Control expects the following from the issued ID/access token:
Scopes¶
| Scope | Purpose |
|---|---|
openid |
Mandatory for OIDC |
profile |
User name / display attributes |
email |
Email address (used in audit logs) |
Configurable via ALETYX_OIDC_SCOPES as a comma-separated list. The default openid,profile,email meets the minimum required set.
Role claim¶
Decision Control reads roles from a single configurable claim path. The default path is roles (top-level claim). Override with ALETYX_OIDC_ROLE_CLAIM_PATH if your provider emits roles under a different path (e.g. realm_access.roles for Keycloak realm roles).
You define your own role values at the provider and map them to Decision Control capabilities through the URL-level RBAC allow-lists (see URL-level RBAC below). Decision Control does not hard-code role names — the values you assign are entirely up to you.
Tenant claim¶
| Variable | Default | Description |
|---|---|---|
ALETYX_OIDC_TENANT_CLAIM_NAME |
tenant_id |
JWT claim carrying the tenant. Set to tid for Entra (Entra emits the tenant in tid). |
ALETYX_OIDC_TENANT_DEFAULT_ID |
aletyx |
Tenant ID used when the token has no tenant claim (single-tenant deployments). Set to your tenant slug. |
Microsoft Entra ID¶
Prerequisites¶
- An Entra tenant with permissions to create app registrations
- A user account allowed to grant tenant-wide admin consent
App registration steps¶
-
Register the application
-
Azure Portal → Microsoft Entra ID → App registrations → New registration
- Name: e.g.
Decision Control — <env> - Supported account types: Accounts in this organizational directory only (single tenant)
- Redirect URI: select Web and enter
https://<dc-host>/auth/callback -
Click Register
-
Record the identifiers
-
From the Overview page, note the Application (client) ID and Directory (tenant) ID. These map to
ALETYX_OIDC_CLIENT_IDandALETYX_OIDC_ENTRA_TENANT_IDrespectively. -
Configure additional redirect URIs (optional)
-
Authentication → Web → Add URI: include any additional environments (
http://localhost:8080/auth/callbackfor local dev, additional cluster hostnames, etc.) -
Generate a client secret
-
Certificates & secrets → Client secrets → New client secret
- Provide a description and expiry (24 months is a common default)
- Copy the
Valueimmediately — Azure displays it only once -
It maps to
ALETYX_OIDC_ENTRA_CLIENT_SECRET -
Grant API permissions
-
API permissions → Add a permission → Microsoft Graph → Delegated permissions
- Add:
openid,profile,email,User.Read -
Click Grant admin consent for
-
Define app roles
-
App roles → Create app role for each role your organization needs (e.g. a viewer role, an author role, an admin role). The role Value is what appears in the token and what you list in the RBAC allow-lists.
-
Assign users to roles
-
Enterprise applications → select the application → Users and groups → Add user/group
- Group-based assignment requires Microsoft Entra ID P1 or higher; Free tenants must assign individual users.
Required configuration variables — Entra¶
| Variable | Required | Example |
|---|---|---|
ALETYX_OIDC_PROVIDER |
yes | entra |
ALETYX_OIDC_CLIENT_ID |
yes | <your-entra-application-client-id> |
ALETYX_OIDC_ENTRA_TENANT_ID |
yes | <your-entra-directory-tenant-id> |
ALETYX_OIDC_ENTRA_CLIENT_SECRET |
yes | <from-step-4> |
ALETYX_OIDC_ENTRA_AUDIENCE |
yes | usually equal to the client ID for v2.0 tokens |
ALETYX_OIDC_TENANT_CLAIM_NAME |
no | set to tid for Entra |
ALETYX_OIDC_SCOPES |
no | defaults to openid,profile,email |
ALETYX_OIDC_ROLE_CLAIM_PATH |
no | defaults to roles |
Example — environment file¶
ALETYX_OIDC_PROVIDER=entra
ALETYX_OIDC_CLIENT_ID=<your-entra-application-client-id>
ALETYX_OIDC_ENTRA_TENANT_ID=<your-entra-directory-tenant-id>
ALETYX_OIDC_ENTRA_AUDIENCE=<your-entra-application-client-id>
ALETYX_OIDC_ENTRA_CLIENT_SECRET=<paste-from-azure>
ALETYX_OIDC_TENANT_CLAIM_NAME=tid
ALETYX_OIDC_SCOPES=openid,profile,email
ALETYX_OIDC_ROLE_CLAIM_PATH=roles
Keycloak¶
Realm and client setup¶
-
Create or select a realm in the Keycloak admin console.
-
Create the client
-
Clients → Create client → Client type: OpenID Connect → Client ID: e.g.
decision-control - Client authentication: ON (confidential client) · Standard flow: ON · Direct access grants: OFF
- Valid redirect URIs:
https://<dc-host>/auth/callback(addhttp://localhost:8080/auth/callbackfor local dev) -
Web origins:
https://<dc-host> -
Capture the client secret
-
Credentials tab → copy the Client Secret. The client ID maps to
ALETYX_OIDC_KEYCLOAK_CLIENT_IDand the secret to its own dedicated variableALETYX_OIDC_KEYCLOAK_CLIENT_SECRET. -
Define and map roles
-
Create client or realm roles, then add a role mapper so the roles land in the token under the claim path you set in
ALETYX_OIDC_ROLE_CLAIM_PATH(rolestop-level, orrealm_access.rolesfor realm roles).
Required configuration variables — Keycloak¶
| Variable | Required | Example |
|---|---|---|
ALETYX_OIDC_PROVIDER |
yes | keycloak |
ALETYX_OIDC_KEYCLOAK_ISSUER_URI |
yes | https://keycloak.example.com/realms/aletyx |
ALETYX_OIDC_KEYCLOAK_JWK_SET_URI |
yes | https://keycloak.example.com/realms/aletyx/protocol/openid-connect/certs |
ALETYX_OIDC_KEYCLOAK_CLIENT_ID |
yes | decision-control |
ALETYX_OIDC_KEYCLOAK_CLIENT_SECRET |
yes | <from-credentials-tab> (omit only for a public client) |
ALETYX_OIDC_SCOPES |
no | defaults to openid,profile,email |
ALETYX_OIDC_ROLE_CLAIM_PATH |
no | defaults to roles; set to realm_access.roles for realm roles |
Example — environment file¶
ALETYX_OIDC_PROVIDER=keycloak
ALETYX_OIDC_KEYCLOAK_ISSUER_URI=https://keycloak.example.com/realms/aletyx
ALETYX_OIDC_KEYCLOAK_JWK_SET_URI=https://keycloak.example.com/realms/aletyx/protocol/openid-connect/certs
ALETYX_OIDC_KEYCLOAK_CLIENT_ID=decision-control
ALETYX_OIDC_KEYCLOAK_CLIENT_SECRET=<paste-from-credentials-tab>
ALETYX_OIDC_SCOPES=openid,profile,email
ALETYX_OIDC_ROLE_CLAIM_PATH=realm_access.roles
URL-level RBAC¶
Decision Control can gate access to specific UI areas and runtime endpoints by role. RBAC is disabled by default; when enabled, each request must carry a role from the relevant per-action allow-list. An empty list locks everyone out of that action, so populate all the lists you intend to use when you turn RBAC on.
| Variable | Default | Gates |
|---|---|---|
ALETYX_OIDC_RBAC_ENABLED |
false |
Master toggle for URL-level RBAC. |
ALETYX_OIDC_RBAC_ROLES_ALLOWED_ACCESS |
(empty) | Access the application at all. |
ALETYX_OIDC_RBAC_ROLES_ALLOWED_PUBLISH |
(empty) | Publish new model versions. |
ALETYX_OIDC_RBAC_ROLES_ALLOWED_ACCESS_AUTHOR |
(empty) | Authoring UI. |
ALETYX_OIDC_RBAC_ROLES_ALLOWED_ACCESS_MANAGE |
(empty) | Management UI. |
ALETYX_OIDC_RBAC_ROLES_ALLOWED_ACCESS_MONITOR |
(empty) | Monitoring UI. |
ALETYX_OIDC_RBAC_ROLES_ALLOWED_ACCESS_PROMPT |
(empty) | Prompt / chat UI. |
ALETYX_OIDC_RBAC_ROLES_ALLOWED_ACCESS_RUN |
(empty) | Runtime endpoints. |
Each allow-list is a comma-separated set of role values (the same values you assigned at the provider). A user is permitted an action if any of their token roles appears in that action's list.
Example (RBAC on, three roles dc-user, dc-author, dc-admin):
ALETYX_OIDC_RBAC_ENABLED=true
ALETYX_OIDC_RBAC_ROLES_ALLOWED_ACCESS=dc-user,dc-author,dc-admin
ALETYX_OIDC_RBAC_ROLES_ALLOWED_ACCESS_AUTHOR=dc-author,dc-admin
ALETYX_OIDC_RBAC_ROLES_ALLOWED_PUBLISH=dc-author,dc-admin
ALETYX_OIDC_RBAC_ROLES_ALLOWED_ACCESS_MANAGE=dc-admin
ALETYX_OIDC_RBAC_ROLES_ALLOWED_ACCESS_MONITOR=dc-admin
ALETYX_OIDC_RBAC_ROLES_ALLOWED_ACCESS_RUN=dc-user,dc-author,dc-admin
ALETYX_OIDC_RBAC_ROLES_ALLOWED_ACCESS_PROMPT=dc-user,dc-author,dc-admin
Server-to-server secret¶
| Variable | Required | Description |
|---|---|---|
ALETYX_OIDC_INTERNAL_SECRET |
yes (if integrated w/ Tower) | Shared secret for Decision Control Tower → Decision Control server-to-server calls. Must be identical on both sides. Leave unset for standalone deployments. |
Dev identity (provider=none)¶
Only used when ALETYX_OIDC_PROVIDER=none. The shared starter installs a synthetic authenticated principal from these values so local/dev runs behave like a real OIDC session (audit trail, RBAC checks, tenant resolution all see a real user). Ignored in entra / keycloak mode, where the identity comes from the JWT. Intended for local diagnostics only; never use in production.
| Variable | Default | Description |
|---|---|---|
ALETYX_OIDC_NONE_USER_ID |
your-user@example.com |
Principal name / user id stamped on audit records and returned by /me when provider=none. The image ships with a placeholder default; override to a value meaningful for your organization. |
ALETYX_OIDC_NONE_DISPLAY_NAME |
Manager |
Display name returned to the UI for the synthetic dev user. |
ALETYX_OIDC_NONE_ROLES |
tower-approvers |
Comma-separated roles granted to the synthetic dev user. Used by the URL-level RBAC allow-lists above. |
Verifying the configuration¶
After deploying, the Tower/DC auth bootstrap endpoint returns the public client metadata expected by the SPA:
A successful response confirms the backend loaded its OIDC configuration. For end-to-end verification, sign in via the browser; Decision Control redirects to the provider, you authenticate, and the provider redirects back to /auth/callback.
Common pitfalls¶
-
Redirect URI mismatch (
AADSTS50011for Entra,Invalid redirect URIfor Keycloak): the redirect URI must exactly match a URI registered at the provider, including the trailing/auth/callback. Common cause: changing the application hostname without updating the registration. -
401on every request after enabling Entra: the UI readsALETYX_OIDC_PROVIDERfrom/env.json, which the backend caches at startup. After changing the provider, restart the pod so the cached/env.jsonis regenerated. If a CDN sits in front, ensure/env.jsonis not cached as a static asset (it contains.json, which can match*.jscache rules). -
RBAC locks everyone out: enabling
ALETYX_OIDC_RBAC_ENABLED=truewith empty allow-lists denies all access. Populate every list you intend to use. -
Roles missing from the token: confirm the user has the role assignment, that the role is mapped into the token, and that
ALETYX_OIDC_ROLE_CLAIM_PATHmatches the path in the token. -
Disabling authentication for diagnostics:
ALETYX_OIDC_PROVIDER=nonebypasses authentication. Intended for local diagnostics only; never use in production.