Skip to content

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

  1. Register the application

  2. Azure Portal → Microsoft Entra IDApp registrationsNew registration

  3. Name: e.g. Decision Control — <env>
  4. Supported account types: Accounts in this organizational directory only (single tenant)
  5. Redirect URI: select Web and enter https://<dc-host>/auth/callback
  6. Click Register

  7. Record the identifiers

  8. From the Overview page, note the Application (client) ID and Directory (tenant) ID. These map to ALETYX_OIDC_CLIENT_ID and ALETYX_OIDC_ENTRA_TENANT_ID respectively.

  9. Configure additional redirect URIs (optional)

  10. AuthenticationWebAdd URI: include any additional environments (http://localhost:8080/auth/callback for local dev, additional cluster hostnames, etc.)

  11. Generate a client secret

  12. Certificates & secretsClient secretsNew client secret

  13. Provide a description and expiry (24 months is a common default)
  14. Copy the Value immediately — Azure displays it only once
  15. It maps to ALETYX_OIDC_ENTRA_CLIENT_SECRET

  16. Grant API permissions

  17. API permissionsAdd a permissionMicrosoft GraphDelegated permissions

  18. Add: openid, profile, email, User.Read
  19. Click Grant admin consent for

  20. Define app roles

  21. App rolesCreate 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.

  22. Assign users to roles

  23. Enterprise applications → select the application → Users and groupsAdd user/group

  24. 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

  1. Create or select a realm in the Keycloak admin console.

  2. Create the client

  3. ClientsCreate clientClient type: OpenID Connect → Client ID: e.g. decision-control

  4. Client authentication: ON (confidential client) · Standard flow: ON · Direct access grants: OFF
  5. Valid redirect URIs: https://<dc-host>/auth/callback (add http://localhost:8080/auth/callback for local dev)
  6. Web origins: https://<dc-host>

  7. Capture the client secret

  8. Credentials tab → copy the Client Secret. The client ID maps to ALETYX_OIDC_KEYCLOAK_CLIENT_ID and the secret to its own dedicated variable ALETYX_OIDC_KEYCLOAK_CLIENT_SECRET.

  9. Define and map roles

  10. 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 (roles top-level, or realm_access.roles for 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:

GET https://<dc-host>/auth/config

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 (AADSTS50011 for Entra, Invalid redirect URI for 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.

  • 401 on every request after enabling Entra: the UI reads ALETYX_OIDC_PROVIDER from /env.json, which the backend caches at startup. After changing the provider, restart the pod so the cached /env.json is regenerated. If a CDN sits in front, ensure /env.json is not cached as a static asset (it contains .json, which can match *.js cache rules).

  • RBAC locks everyone out: enabling ALETYX_OIDC_RBAC_ENABLED=true with 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_PATH matches the path in the token.

  • Disabling authentication for diagnostics: ALETYX_OIDC_PROVIDER=none bypasses authentication. Intended for local diagnostics only; never use in production.