Skip to content

feat: add generic OpenID Connect (OIDC) auth provider#805

Open
we4sz wants to merge 11 commits into
obot-platform:mainfrom
we4sz:feat/generic-oidc-auth-provider
Open

feat: add generic OpenID Connect (OIDC) auth provider#805
we4sz wants to merge 11 commits into
obot-platform:mainfrom
we4sz:feat/generic-oidc-auth-provider

Conversation

@we4sz
Copy link
Copy Markdown

@we4sz we4sz commented May 30, 2026

What

Adds a generic OpenID Connect auth provider (generic-oidc-auth-provider) so Obot can authenticate users against any OIDC-compliant identity provider — Keycloak, Authentik, Dex, Auth0, Zitadel, etc. — by discovering endpoints from the issuer's /.well-known/openid-configuration.

Why

The OSS edition currently ships only Google and GitHub auth providers. Self-hosters who run their own IdP (very commonly Keycloak) have no way to use it for hub login without the enterprise edition. This provider closes that gap with a standards-based OIDC implementation.

How

  • Mirrors the existing google-auth-provider exactly, wrapping oauth2-proxy's built-in oidc provider (OIDCIssuerURL, OIDCEmailClaim, OIDCGroupsClaim).
  • Registered in index.yaml under authProviders.
  • Exposes the standard Obot provider shim endpoints (/obot-get-state, /obot-get-user-info, /obot-list-user-auth-groups); user info is fetched from the discovered userinfo_endpoint.

Configuration

Field Required Default
Issuer URL
Client ID / Client Secret
Allowed E-Mail Domains *
Scopes optional openid email profile
Email / Groups claim optional email / groups
Allow Unverified Email optional true

Decision to flag for reviewers: Allow Unverified Email defaults to true because many self-hosted IdPs (Keycloak included) don't set email_verified, which would otherwise block all logins. It's fully configurable — happy to flip the default to false if you'd prefer safe-by-default.

Testing

  • go vet ./... clean; unit tests added (pkg/profile) for userinfo discovery/fetch and lenient email_verified parsing (bool/string).
  • Verified end-to-end against a Keycloak realm: the provider appears in the admin Auth Providers UI, configures correctly, initiates the OIDC flow against the realm, and the IdP issues a valid ID token with email/preferred_username/name claims consumed by oauth2-proxy.

🤖 Generated with Claude Code

we4sz and others added 11 commits May 30, 2026 11:47
Adds `generic-oidc-auth-provider`, a generic OIDC auth provider that works
with any OIDC-compliant IdP (Keycloak, Authentik, Dex, Auth0, etc.) by
discovering endpoints from the issuer's /.well-known/openid-configuration.

Today the OSS edition only ships Google and GitHub auth providers, so
self-hosters who use their own IdP (commonly Keycloak) cannot use it for hub
login. This provider closes that gap. It mirrors the existing
google-auth-provider, wrapping oauth2-proxy's built-in OIDC provider, and is
registered in index.yaml under authProviders.

Config: Issuer URL, Client ID/Secret, Email Domains (required); Scopes
(default "openid email profile"), Email/Groups claims, and Allow Unverified
Email (default true, since many self-hosted IdPs don't set email_verified).

Includes unit tests for userinfo discovery/fetch and email_verified parsing.
Implements the group-listing shim endpoint to return the authenticated
user's groups (from the configured groups claim / userinfo) instead of 404.
This lets Obot enumerate groups for group-scoped MCP registries and group
role assignments. Generic OIDC has no "list all groups" endpoint, so this
reports the caller's own groups — sufficient for Obot to populate the groups
it has seen across logins.
…userinfo

Avoids spurious userinfo 401s when the access token is near/just expired by
reading the groups claim directly from the JWT (signature already verified
upstream by oauth2-proxy). Falls back to the userinfo endpoint for opaque tokens.
list-user-auth-groups now enumerates realm groups via Keycloak's Admin API
using the OIDC client's own service-account (client_credentials), so Obot can
populate the group picker for group-scoped registries and group role
assignments. Requires the client to have a service account with
query-groups/view-users; falls back to the caller's own token groups (and
to userinfo) for non-Keycloak issuers or missing permissions.
Keycloak (v23+) only returns top-level groups in the groups listing; nested
groups (e.g. offices/DEVBORAS) carry subGroupCount but no inline subGroups.
Fetch each group's children via the /children endpoint and recurse so the
full group tree is enumerable for group-scoped registries/role assignments.
Render the Keycloak group path as "parent / child" in the group picker
(e.g. "offices / DEVBORAS") so nested groups read as subgroups in Obot's
flat list. ID stays the leaf name to match the token's groups claim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Obot calls TWO group endpoints; the provider only implemented one, under
the wrong name:

- /obot-list-auth-groups (GET): enumerate ALL realm groups for the admin
  group picker. The provider never served this path, so the picker fell
  back to Obot's DB cache (leaf names only). Now implemented via the
  Keycloak Admin API with hierarchical "parent / child" display names.

- /obot-list-user-auth-groups (POST, body=provider user ID): the groups a
  SPECIFIC user belongs to, used to sync per-user memberships. The handler
  previously returned ALL realm groups here, which would make every user a
  member of every group. Now looks up only that user's groups via
  /admin/realms/{realm}/users/{id}/groups.

Refactors the shared client_credentials token + path-name rendering into
keycloakAdmin/groupDisplayName/fetchKeycloakGroups helpers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the implicit "/realms/ in issuer" sniff with an explicit
OBOT_OIDC_AUTH_PROVIDER_GROUP_ADMIN env var (only supported value:
keycloak). This keeps the provider generic OIDC by default — groups come
from the token claim for any IdP, exactly like the OSS Google/GitHub
providers — and makes the vendor-specific group-admin features (enumerate
all groups for the picker + resolve a user's real memberships via the
Keycloak Admin API) an unambiguous opt-in. Exposed as an optional config
field in tool.gpt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Accept the canonical "keycloak" plus common truthy values (true/1/yes/
on/enabled) for OBOT_OIDC_AUTH_PROVIDER_GROUP_ADMIN, and treat anything
unrecognized (blank, typo, unsupported backend) as off — which only
disables group enumeration, never blocks login. Obot's provider config
form is text-only (no native dropdown), so the field label/description now
make the expected value explicit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review hardening of /obot-list-user-auth-groups:

- On a Keycloak admin-API lookup failure, return an error (502) instead of
  an empty 200. Obot syncs a user's group memberships from this endpoint
  and treats an empty list as "member of no groups", deleting their
  memberships — so a transient Keycloak failure during a periodic re-check
  would wipe them. An error makes Obot skip the sync and keep them.
- When the Keycloak group admin is not opted in, return 404 (as the OSS
  Google/GitHub providers do) rather than an empty list. Generic-OIDC
  groups already reach Obot via the token claim in /obot-get-state.
- Remove the now-unreachable JWT bearer-token group fallback: Obot calls
  this endpoint with only the provider user ID and no Authorization header.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow Keycloak's first/max pagination in fetchKeycloakGroups (page size
100) until a short page is returned, instead of capping every query at
max=1000. Realms, group children, and user memberships larger than one
page are now fully enumerated rather than silently truncated. Applies to
all three admin queries (realm groups, /children, user groups).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant