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
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 namedops(case ignored), but notdevopsorteam-ops— the token must equal the group name in full. An emptyadmin-groupssilently leaves the deployment with no LDAP ADMIN path. See LDAP → Admin promotion.GitHub
admin-principalsbypassesorganization-name. A login inadmin-principalsis grantedADMINregardless 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-groupsis a full team-name match. Same shape as LDAP —admin-groups: [admins]promotes members of a team namedadmins(case ignored), but notteam-admins,admins-readonly, ordata-admins. Enter each admin team's name exactly as it appears in GitHub. See OAuth — GitHub.GitHub Enterprise Server not supported.
api.github.comis hard-coded in the GitHub handler with no override knob; GHES deployments cannot use the GitHub provider today. See OAuth — GitHub.Google
admin-groupsis a silent no-op. The field binds without error but the Google handler never reads it; promote withadmin-principalsoradmin-attribute. See OAuth — Google.Okta / Keycloak / Custom OIDC do detect
admin-principals, butadmin-groupsneedsgroups-claim. The generic Custom OIDC handler promotes a login whose principal is listed inadmin-principals(case-insensitive, full match). It readsadmin-groupsonly when you also setgroups-claim, because this handler has no default groups claim. With neither set, every login isUSERand promotion is manual via the Management UI. See OAuth — Other OIDC providers.LOGIN_FORMSECURITY_RULES are inert. Every authenticated user isADMIN; the Policy/Role table you author via Management UI does not gate the live auth chain in this mode. See Login form → Authorization posture.DISABLEDsynthetic 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/whoamivanishes — anonymous callers stop seeing ADMIN. Before flipping, read Disabled authentication → Migrating away from DISABLED and audit anyOwnerrows whoseOIDC_USERNAMEmatches the literaladminorADMIN(collision with the synthetic identities). Any RBACPolicyorRoleyou authored while DISABLED was active was never consulted — they take effect for the first time after the migration.LOGIN_FORM→OAUTH2orLDAP. Policies become consulted for the first time (the LOGIN_FORM chain omits theAuthorizationCustomizer). 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.LDAP→OAUTH2.admin-groupsmatching 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 matchescognito:groups, Azure matches theroles(orgroups) claim. Re-enteradmin-groupsusing 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 namedADMIN, 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.
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