Admin promotion across providers

Single reference for ADMIN-promotion divergence across the four auth modes and the six OAuth2 provider sub-shapes — the matrix, the per-provider knobs that look the same but behave differently.

ODD Platform has four top-level authentication modes (DISABLED, LOGIN_FORM, OAUTH2, LDAP), and the OAUTH2 mode supports six provider sub-shapes (AWS Cognito, GitHub, Google, Azure AD, ODD_IAM, plus a Custom OIDC fallback that Okta and Keycloak deployments use today). Whether a given user becomes ADMIN on login depends on the mode, the provider, and a handful of configuration knobs whose names look the same across providers but behave differently per provider runtime.

This page is the single reference for that divergence. Use it to (1) understand what a configured value will actually do on your provider, (2) plan migrations between auth modes without losing or accidentally widening ADMIN grants, and (3) pick a provider for a new deployment based on the precision of the admin grant.

Per-provider operational details live on the provider's own page; this page focuses on the comparison.

Admin-promotion matrix

#
Mode / provider
Admin-detection mechanism
Match semantic
Notable caveat

1

LDAP

auth.ldap.groups.admin-groups against the user's LDAP group memberships

case-insensitive, full group-name match

A token must equal a group name in full (case ignored): admin-groups: [ops] promotes members of ops / Ops / OPS but not devops or team-ops. If admin-groups is empty, every user is USER with no path to ADMIN

2

OAUTH2 → AWS Cognito

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

case-insensitive, full match

The match ignores case (Admins and admins both match) but requires the whole value — it is not a substring match

3

OAUTH2 → GitHub

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

case-insensitive, full match (both principals and groups)

admin-principals bypasses organization-name; a team token must equal a team name in full (e.g. admins does not match team-admins); GitHub Enterprise Server unsupported (api.github.com hard-coded)

4

OAUTH2 → Google

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

case-insensitive, full match

The match ignores case but requires the whole value. admin-groups is a silent no-op — the field binds without error but is never read by the Google handler

5

OAUTH2 → Azure AD

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

case-insensitive, full match

The match ignores case but requires the whole value — it is not a substring match

6

OAUTH2 → ODD_IAM

userinfo flag from the IAM provider

flag-based

(7)

LOGIN_FORM

None — every authenticated user is granted ADMIN authority unconditionally

The SECURITY_RULES table is not consulted in this mode; authored Policies do not gate the live auth chain. See Login form → Authorization posture.

Okta, Keycloak, any other OIDC provider

Custom OIDC handler — evaluates admin-principals always, and admin-groups only if you set groups-claim

case-insensitive, full match

admin-principals promotes at login with no extra configuration. admin-groups is inert unless groups-claim is set, because this handler has no default groups claim. If neither is configured, every user is USER and ADMIN promotion is a manual Owner-Role binding via the Management UI

DISABLED

Synthetic admin — GET /api/identity/whoami returns { username: "admin", permissions: [ … every Permission enum value … ] } to any anonymous caller

The permission set is Permission.values() so a new platform capability is automatically granted. See Disabled authentication → What's anonymously reachable.

Provider-specific gotchas

Each item below points at the page that owns the operational detail; this list is the cross-cutting summary so an operator scanning for "what bites me on each provider" has one reading surface.

  • LDAP group-name match is full-string + groups-unset silent USER-only. admin-groups: [ops] promotes members of a group named ops (case ignored), but not devops or team-ops — the token must equal the group name in full. An empty admin-groups silently leaves the deployment with no LDAP ADMIN path. See LDAP → Admin promotion.

  • GitHub admin-principals bypasses organization-name. A login in admin-principals is granted ADMIN regardless of organization membership — a typo or stale entry that matches an attacker-registerable GitHub login is a platform-ADMIN backdoor. See OAuth — GitHub.

  • GitHub admin-groups is a full team-name match. Same shape as LDAP — admin-groups: [admins] promotes members of a team named admins (case ignored), but not team-admins, admins-readonly, or data-admins. Enter each admin team's name exactly as it appears in GitHub. See OAuth — GitHub.

  • GitHub Enterprise Server not supported. api.github.com is hard-coded in the GitHub handler with no override knob; GHES deployments cannot use the GitHub provider today. See OAuth — GitHub.

  • Google admin-groups is a silent no-op. The field binds without error but the Google handler never reads it; promote with admin-principals or admin-attribute. See OAuth — Google.

  • Okta / Keycloak / Custom OIDC do detect admin-principals, but admin-groups needs groups-claim. The generic Custom OIDC handler promotes a login whose principal is listed in admin-principals (case-insensitive, full match). It reads admin-groups only when you also set groups-claim, because this handler has no default groups claim. With neither set, every login is USER and promotion is manual via the Management UI. See OAuth — Other OIDC providers.

  • LOGIN_FORM SECURITY_RULES are inert. Every authenticated user is ADMIN; the Policy/Role table you author via Management UI does not gate the live auth chain in this mode. See Login form → Authorization posture.

  • DISABLED synthetic admin is anonymous. No credentials needed; Permission.values() dynamically expands so new capabilities auto-enter the grant. See Disabled authentication → What's anonymously reachable.

Migration-mode notes

When you move between auth modes, the admin-detection rules change underneath you. The notes below name the most common transitions:

  • DISABLED → any production mode (LOGIN_FORM, OAUTH2, LDAP). The synthetic-admin response from /api/identity/whoami vanishes — anonymous callers stop seeing ADMIN. Before flipping, read Disabled authentication → Migrating away from DISABLED and audit any Owner rows whose OIDC_USERNAME matches the literal admin or ADMIN (collision with the synthetic identities). Any RBAC Policy or Role you authored while DISABLED was active was never consulted — they take effect for the first time after the migration.

  • LOGIN_FORMOAUTH2 or LDAP. Policies become consulted for the first time (the LOGIN_FORM chain omits the AuthorizationCustomizer). Test the production-mode Policy table BEFORE migrating; under LOGIN_FORM every user was ADMIN so Policy bugs were invisible. Cross-mode username collisions surface — see the cross-mode user-name collision note.

  • LDAPOAUTH2. admin-groups matching is full group-name equality (case ignored) under both LDAP and every OAuth2 provider, so the matching semantic does not change. What changes is the source of the names: LDAP matches your directory's group names, GitHub matches GitHub team names, Cognito matches cognito:groups, Azure matches the roles (or groups) claim. Re-enter admin-groups using the new provider's group names; the LDAP group names will not match unless they happen to be identical.

  • Adding S2S to any production mode. The S2S filter's synthetic principal is literal uppercase ADMIN. If you have an existing user named ADMIN, that user's owner mapping becomes the S2S caller's owner — see S2S → Operator caveats.

Choosing a provider (decision guide)

For a new deployment, the table below summarises the trade-offs operators most often weigh. Across every mode and provider, admin-groups / admin-principals matching is case-insensitive, full-value equality — a configured token grants ADMIN only when it equals a group name (or principal) in full, with case ignored. There is no substring or prefix matching, so a short token like ops cannot accidentally promote devops. The columns instead compare what is most worth weighing per provider — whether admin-groups works at all on that provider, and whether the platform revokes the token at the IdP on sign-out. Logout revocation is whether the platform asks the IdP to revoke the access token on sign-out — see the logout token-revocation matrix for details.

All providers match admin-groups / admin-principals the same way — case-insensitive, full-value equality. The column below therefore compares which knobs each provider actually honours, not match precision.

Mode / provider
Admin grant by
Logout revocation
Best fit for

LOGIN_FORM

n/a — every user is ADMIN

n/a — local session only

Dev / demo over HTTPS terminating at a trusted proxy

LDAP

admin-groups (group names)

n/a — bind-only auth

Existing LDAP / Active Directory shop

OAUTH2 → AWS Cognito

admin-principals + admin-groups

no (the platform does not call /oauth2/revoke today)

AWS-first deployments; pair with short token TTL

OAUTH2 → GitHub

admin-principals + admin-groups (team names)

yes

github.com-hosted (not GHES) teams

OAUTH2 → Google

admin-principals only (admin-groups is a no-op)

yes

Google Workspace organisations

OAUTH2 → Azure AD

admin-principals + admin-groups

no (Azure v2.0 lacks RFC 7009 — protocol-level)

Microsoft 365 / Entra ID shops

OAUTH2 → ODD_IAM

userinfo flag from the IAM provider

no

Vendor-managed ODD deployments

OAUTH2 → Okta / Keycloak / Custom OIDC

admin-principals (+ admin-groups if groups-claim set)

no

Existing OIDC IdPs

DISABLED

n/a — anonymous synthetic admin

n/a

Local-only dev; never for any reachable network

If your security posture requires IdP-level token revocation on logout, Google and GitHub are today's best matches. If you grant ADMIN by group membership, every mode except Google supports admin-groups (Custom OIDC requires groups-claim to be set). For any production deployment, audit the trade-offs above against your incident-response model before choosing.

Last updated