For the complete documentation index, see llms.txt. This page is also available as Markdown.

OAUTH2/OIDC

Configure ODD Platform to authenticate users via OAuth2/OIDC against AWS Cognito, GitHub, Google, Azure AD, Okta, Keycloak, or any custom OIDC-compliant identity provider.

ODD Platform supports different OIDC/OAuth2 providers. Currently there are:

It is possible to have multiple providers at the same time (e.g. you want to allow to authenticate users from Github and Google, or from multiple Cognito user pools). Configuration properties name for each provider must fit the pattern auth.oauth2.client.{client_id}.{client_parameter}, where client_id is provider identifier.

There are some common parameters which are used across all providers:

  • auth.type. Must be set to OAUTH2

  • auth.oauth2.client.{client-id}.provider. Provider code, which helps application to understand which provider is used.

  • auth.oauth2.client.{client-id}.client-id. Client ID obtained from provider

  • auth.oauth2.client.{client-id}.client-secret. Client secret obtained from provider

  • auth.oauth2.client.{client-id}.client-name. Custom name, which will be shown on UI in case of multiple providers enabled. (optional)

  • auth.oauth2.client.{client-id}.redirect-uri. Redirect URL. Must be defined as {domain}/login/oauth2/code/{client-id}

  • auth.oauth2.client.{client-id}.scope. Authorization scopes which are allowed for application

  • auth.oauth2.client.{client-id}.issuer-uri. URI that can either be an OpenID Connect discovery endpoint or an OAuth 2.0 Authorization Server Metadata endpoint defined by RFC 8414.

Given that the issuer uri is composed of a host and a path, ODD Platform tries to fetch information, calling following URLs:

  • host/.well-known/openid-configuration/path

  • issuer/.well-known/openid-configuration

  • host/.well-known/oauth-authorization-server/path

If you don't have issuer uri or if you want to override some values, there are special properties, which should be defined:

  • auth.oauth2.client.{client-id}.authorization-uri.Authorization URI for the provider.

  • auth.oauth2.client.{client-id}.token-uri.Token URI for the provider.

  • auth.oauth2.client.{client-id}.user-info-uri.User info URI for the provider.

  • auth.oauth2.client.{client-id}.jwk-set-uri.JWK set URI for the provider.

If issuer uri can provide this info above parameters might be skipped.

  • auth.oauth2.client.{client-id}.username-attribute. Defines which token claim should be picked as username in ODD Platform

  • auth.oauth2.client.{client-id}.admin-attribute. Defines which token claim is responsible for admin principal

  • auth.oauth2.client.{client-id}.admin-principals. List of users, who will have ADMIN role on login (for detailed explanation please check the Roles section).

  • auth.oauth2.client.{client-id}.pkce. Optional Boolean (default unset, i.e. disabled). Enables Proof Key for Code Exchange (RFC 7636) for the authorization code flow. When pkce: true and client-secret is empty, the platform registers the client as a public OAuth2 client (client_authentication_method=none) and PKCE protects the code exchange. When client-secret is set, the platform always uses confidential-client authentication and the pkce flag has no effect on the registration. Most commonly required by Keycloak — see the Keycloak with PKCE example below.

Admin-detection per-provider matrix

The configuration POJO that backs auth.oauth2.client.{id} is uniform across every provider, but each provider's runtime handler reads a different subset of the configured fields. The matrix below summarises how admin-principals and admin-groups actually behave for every supported provider — the silent-no-op rows in particular catch operators by surprise because the configuration loads without error. For the same comparison across all auth modes (not just OAuth2), see Admin promotion across providers.

Provider
Admin detection in code
Match semantic
Silent caveats

AWS Cognito

admin-principals (email / configured username-attribute) and admin-groups (against the cognito:groups claim)

exact match on both

GitHub

admin-principals (login) before the organization-name check; admin-groups after, against team names returned by /user/teams

exact (principals); case-insensitive full match (groups)

admin-principals bypasses organization-name; GitHub Enterprise Server is not supportedapi.github.com is hard-coded in the handler

Google

admin-principals (email by default; overridable via admin-attribute)

exact match

admin-groups is a silent no-op — the field binds without error but the handler never reads it; use admin-principals or admin-attribute

Azure AD

admin-principals (against admin-attribute) and admin-groups (against the roles claim by default; switch to groups via groups-claim)

exact match

Okta

admin-principals always (against the admin-attribute / username-attribute claim); admin-groups only when groups-claim is set

case-insensitive full match

admin-groups is inert unless groups-claim is configured — the generic OIDC handler has no default groups claim. admin-principals works with no extra config

Keycloak

same as Okta — admin-principals always; admin-groups only when groups-claim is set

case-insensitive full match

same as Okta — admin-groups needs groups-claim

Custom OIDC

admin-principals always; admin-groups only when groups-claim is set

case-insensitive full match

no default groups claim — set groups-claim to use admin-groups; admin-principals needs no extra config

Reading the matrix: if your provider row says "silent caveats", the configured admin-groups / admin-principals are not enforced the way the property names suggest. The provider-specific sections below repeat each caveat at the point of configuration; this matrix is the single comparison view.

AWS Cognito

AWS Cognito provider can be configured using common oauth properties and couple of provider specific properties:

  • auth.oauth2.client.{client-id}.admin-groups. List of admin groups. Groups are retrieved from cognito:groups token claim.

  • auth.oauth2.client.{client-id}.logout-uri. Application will be redirected to this URI after user logout for removing session on cognito side. Please check AWS Docs for more details.

auth.oauth2.client.{client-id}.username-attribute is cognito:username by default

Github

You can use Github as your OAUTH provider. ODD platform can retrieve info about user organizations and teams and use it for granting ADMIN permissions (for detailed explanation please check the Roles section). There are some github specific properties, which can be set:

  • auth.oauth2.client.{client-id}.organization-name. Restricts login only for users from this particular organization

  • auth.oauth2.client.{client-id}.admin-groups. Grants admin privilegies for users who are members of these teams, which are inside above organization

Google

ODD Platform allows to authenticate users via Google. You can restrict users to login under your organization domain. This is controlled by auth.oauth2.client.{client-id}.allowed-domain property.

Azure AD

ODD Platform supports integration with Azure Active Directory (Azure AD) using OAuth2/OpenID Connect (OIDC). Azure AD applications can be registered in one of two modes:

  • Single-tenant — only users from one specific Azure AD tenant can sign in. Use this for organisation-internal deployments.

  • Multi-tenant — users from any Azure AD tenant can sign in. Use this when the platform is hosted as a service for several organisations.

The two modes share the same properties; they differ only in how the issuer-uri, authorization-uri, token-uri, and logout-uri are constructed. Examples for both forms are given below.

Prerequisites: Azure AD app registration

Before configuring ODD Platform, register an application in Azure AD:

  1. Go to Azure Active DirectoryApp registrationsNew registration.

  2. Choose the supported account types (single-tenant vs. multi-tenant) that match the deployment.

  3. Add a Web redirect URI: {host}/login/oauth2/code/azure.

  4. Under Certificates & secrets, generate a client secret and store its value — it cannot be viewed again later.

  5. Under API permissions, add the following Microsoft Graph delegated permissions and grant admin consent: openid, offline_access, User.Read. Add email and profile as well if the profile/email scopes are requested below.

  6. (Optional) To use admin-groups, choose one of the following:

    • Azure AD App Roles (default, recommended) — under App roles, create the roles you want to grant ADMIN (for example Admins, Managers), then assign users or groups to those roles. Azure will emit the role values in the roles claim of the ID token, which is what ODD Platform reads by default.

    • Azure AD security groups — under Token configurationAdd groups claim, include the groups claim in the ID token. You must also set groups-claim: groups in the ODD configuration (see the note under admin-groups below), because the Azure handler reads from roles unless told otherwise.

  7. Note the Application (client) ID and Directory (tenant) ID — both are needed by the configuration below.

Single-tenant configuration

Use the tenant-specific issuer-uri; {azure_tenant_id} is your Directory (tenant) ID. Spring Security will discover authorization-uri, token-uri, jwk-set-uri, and user-info-uri from the issuer's OpenID Connect discovery document, so they do not need to be set explicitly. jwk-set-uri is still shown in the example because it can be required when the discovery endpoint is unreachable (for example, from air-gapped networks).

Multi-tenant configuration

For a multi-tenant application, the OpenID Connect discovery document is not served under a tenant-specific URL, so issuer-uri cannot be used. Instead, override authorization-uri, token-uri, jwk-set-uri, and logout-uri to point at the organizations endpoint (use common if the app should also accept personal Microsoft accounts). azure-tenant-id must still be set to the tenant that owns the app registration.

Notes:

  • Ensure the openid scope is always included, as it is mandatory for OIDC.

  • The azure-tenant-id should correspond to the Azure AD tenant that owns the app registration.

  • The jwk-set-uri is mandatory for Azure to function correctly with ODD Platform when the discovery endpoint is unreachable.

  • logout-uri is the Azure AD OpenID Connect logout endpoint — https://login.microsoftonline.com/{azure_tenant_id}/oauth2/v2.0/logout for single-tenant and https://login.microsoftonline.com/organizations/oauth2/v2.0/logout (or the common variant if the app also accepts personal Microsoft accounts) for multi-tenant. On logout, ODD Platform redirects the browser here so Azure AD can end its own session and then return the user to the Platform.

  • admin-principals is the list of user identifiers (matched against the admin-attribute claim, email in the examples above) that will be granted the ADMIN role on login.

  • admin-groups grants the ADMIN role to every user whose token contains one of the listed values. By default, ODD Platform's Azure handler reads these values from the roles claim, which Azure AD populates from App roles assigned to the user. The values in admin-groups must match the app role value fields, not Azure AD display names.

    • To grant ADMIN based on Azure AD security group membership instead, set groups-claim: groups in the Azure configuration and add the groups claim to the ID token in Azure (Token configurationAdd groups claim). The admin-groups list then matches against values emitted in the groups claim (group object IDs by default — switch the Azure claim output to Group Name in the Token configuration dialog if you want to match on group display names).

  • If an external user's login doesn't provide the email attribute by default, ensure that the user exists as an external guest in Azure AD associated with an email.

Troubleshooting Tips:

  • If you encounter errors regarding the missing email attribute, ensure the user exists in Azure AD as a properly configured external guest user with an email attribute.

  • For single-tenant deployments, always verify that issuer-uri, jwk-set-uri, user-info-uri, and logout-uri correspond to your tenant ID.

  • If logout returns a 500 error or never completes, verify that logout-uri is set and matches your single-tenant / multi-tenant choice. An unset logout-uri triggers a NullPointerException in the Azure logout handler; the browser sees a 500 while the user remains signed in on both sides.

  • If admin-groups has no effect, check which claim is being read:

    • App Roles (default) — decode the ID token at jwt.ms or equivalent; confirm the roles claim is present and contains the app-role value fields that your admin-groups list references. If the roles claim is absent, verify that you have assigned users to app roles under Enterprise applications → your app → Users and groups.

    • Security groups — confirm that groups-claim: groups is set in the ODD configuration (without this, admin-groups is matched against roles, not groups), and that the groups claim is emitted by the Azure token configuration.

Other OIDC providers

ODD Platform doesn't have any specific parameters for other providers, so they can be easily configured using default parameters. You can check examples below for OKTA and Keycloak OIDC providers.

Okta, Keycloak, and any other "Custom OIDC" provider DO promote to ADMIN via admin-principals. These providers fall through to the generic CustomOIDCUserHandler, which evaluates admin-principals on every login: a user whose admin-attribute (or username-attribute) claim matches a configured admin-principals entry — case-insensitive, full match — is promoted to ADMIN with no extra configuration. admin-groups is also honoured, but only if you set groups-claim explicitly, because the generic handler has no default groups claim. With neither admin-principals nor (groups-claim + admin-groups) configured, every authenticated user is granted USER and promotion is a manual Owner-Role binding (an ADMIN-equivalent Role bundle) through the Management UI. The Okta and Keycloak YAML examples below set admin-principals, consistent with this.

Keycloak with PKCE

Keycloak realms can be configured to require Proof Key for Code Exchange (PKCE, RFC 7636) on the authorization code flow — most commonly for public clients (browser-based or SPA-style flows that cannot keep a client secret).

To enable PKCE, set pkce: true on the Keycloak provider configuration and leave client-secret empty. The platform then registers the client as public (client_authentication_method=none) and Spring Security performs the code exchange with PKCE.

The pkce property is declared on ODD's generic OAuth2 provider configuration, so it is technically available for any provider — not just Keycloak. In practice, Keycloak is the realm most commonly configured to require PKCE.

Logout token-revocation matrix

When an operator clicks Sign out in the ODD UI, the platform invalidates the local server-side session and — depending on the provider — optionally asks the identity provider (IdP) to revoke the OAuth2 access token. Whether the IdP-issued token is actually revoked differs per provider, and the residual-token-validity window can be substantial. Operators on shared or public workstations need to know which providers leave a usable token behind after a "successful" logout.

Provider
Local session invalidated
IdP access token revoked
If not revoked, residual validity

AWS Cognito

yes

no — the handler redirects to Cognito's /logout for session removal but does not call /oauth2/revoke

access token ~1 hour; refresh token up to 90 days, depending on the User Pool configuration

GitHub

yes

yes — the handler calls DELETE /applications/{client_id}/grant to revoke the OAuth grant on the user's behalf

Google

yes

yes — the handler POSTs to oauth2.googleapis.com/revoke for the access token

Azure AD

yes

no — Azure AD v2.0 does not expose RFC 7009 token revocation (protocol-level limitation); the handler can only redirect to the OIDC end-session endpoint

access token ~1 hour; refresh token per tenant policy

ODD_IAM

yes

no — session invalidation only by design (the IAM provider does not implement revocation)

Okta / Keycloak / Custom OIDC

yes

no — there is no dedicated logout handler; the platform invalidates only the local session

per IdP policy on the access token; refresh tokens persist until they expire

Operator caution. For any row in the IdP access token revoked: no column, a token captured by an attacker before logout — for example, from browser-side credential exfiltration on a shared terminal — remains usable against IdP-protected resources for the remainder of the token's natural validity. The ODD platform's session invalidation does not affect this; the only mitigation in those modes is to wait for the token to expire or to sign out at the IdP directly (via the Cognito hosted UI, the Azure portal session controls, etc.).

A platform-side fix for the Cognito gap — calling /oauth2/revoke from the Cognito logout handler — is tracked upstream.

Post-logout redirect derivation

All five OAuth2 logout handlers (Cognito, Github, Google, Azure, ODD_IAM) build the post_logout_redirect_uri (or, for the providers that use a different name, the equivalent return-URL parameter) from the inbound request's URI. The platform reads scheme, host, port, path, and query from the inbound HTTP request and reconstructs the base URL with / as the path — there is no allowlist of trusted hosts, no platform.base-url-style configuration property, and no scheme enforcement.

In a typical deployment where the inbound Host header is the browser-presented value and the platform's reverse proxy passes it through unchanged, the post-logout redirect points back to the same origin and there is nothing to worry about. The concern surfaces when the reverse proxy trusts user-controlled Host or X-Forwarded-Host headers, or when the IdP's post-logout-redirect allowlist is wider than the platform's host.

UI feedback on logout outcome

The SPA's Sign out action sends the user to /logout via a hard navigation; the platform handles the rest. The SPA does not distinguish between "logged out + IdP token revoked" (Google / GitHub) and "logged out + IdP token still valid" (Cognito / Azure / ODD_IAM) in any UI-visible signal. Operators on shared or public terminals who depend on revocation should not infer it from the absence of an error; sign out from the IdP directly (Cognito hosted UI, Azure portal, etc.) in addition to ODD logout when the workstation is not yours. Per-provider feedback messages in the SPA are tracked upstream.

Last updated