Skip to content

feat: built-in OIDC authentication for self-hosted - #4176

Open
awapf wants to merge 8 commits into
hatchet-dev:mainfrom
awapf:oidc-pr
Open

feat: built-in OIDC authentication for self-hosted#4176
awapf wants to merge 8 commits into
hatchet-dev:mainfrom
awapf:oidc-pr

Conversation

@awapf

@awapf awapf commented Jun 13, 2026

Copy link
Copy Markdown

Closes #3052. Supersedes #3607 (and the earlier #1139), building on @yaswanthkumar1995's work and carrying it to the finish line.

Adds a generic OIDC provider so self-hosted instances can authenticate against any OIDC-compliant IdP that issues an email claim (Keycloak [tested], Authentik, Dex, Okta, Microsoft Entra ID [tested], …), following the exact pattern of the existing Google/GitHub OAuth handlers — as @abelanger5 noted on Discord, "OIDC is a small lift on top of the existing OAuth."

What it does

  • New oidc provider: /api/v1/users/oidc/{start,callback}, mirroring the Google/GitHub handlers. Uses coreos/go-oidc/v3 with OIDC discovery from an issuer URL and proper ID-token verification (JWKS), plus a UserInfo fallback and a sub match.
  • Config (opt-in, disabled by default — cannot affect existing deployments): SERVER_AUTH_OIDC_ENABLED, _CLIENT_ID, _CLIENT_SECRET, _ISSUER_URL, _SCOPES (default openid profile email).
  • Frontend SSO button on the auth page, coexisting with the existing sso (control-plane) provider — they're orthogonal; this is purely self-hosted.
  • Respects restrictedEmailDomains, allowSignup, and emailVerified.
    • Requires an email claim (Hatchet identifies users by email). email_verified is honored when present; providers that don't emit it (e.g. Microsoft Entra ID) are auto-verified via the existing SERVER_AUTH_SET_EMAIL_VERIFIED, or left for the dashboard's verify-email gate — consistent with the Google/GitHub handlers. Closes Enable Microsoft Entra ID OAuth authentication #2648.

What's new vs #3607

This is #3607 rebased on main, plus the things needed to actually merge it:

  1. Bug fix: OAuthOpts.Provider was validated as oneof=google github sso, so creating a user with provider="oidc" failed after a successful login and ID-token verification. Added oidc to the allowed set.
  2. RBAC: registered UserUpdateOidcOauthStart/Callback in api/v1/server/authz/rbac.yaml (public, like the Google/GitHub routes) — the API refuses to start otherwise.
  3. Tests (feat: add built-in OIDC authentication support #3607/feat: Added Custom Auth #1139 had none): an in-process mock OIDC issuer (discovery + JWKS + signed ID tokens) drives a unit test (go test: ID-token verification accepts a valid token, rejects wrong-audience / missing id_token) and an integration test (go test -tags integration: user create + update upsert against a real DB — the exact path the validator bug broke).
  4. Regenerated openapi.gen.go.
  5. Email verification: the callback no longer hard-rejects an unverified email (the Google/GitHub handlers don't either). It stores emailVerified = claims.EmailVerified || SetEmailVerified, so providers without an email_verified claim (e.g. Entra ID) work — auto-verified via the existing instance setting, or behind the verify-email gate for admin approval.

task test and task test-integration are green. Verified end-to-end against a live Keycloak (login → code exchange → JWKS verification → user upsert → session).

Dependencies / licensing

Adds coreos/go-oidc/v3 and (transitively) go-jose/go-jose/v4, both Apache-2.0 — compatible with the project's MIT license, and the same family of permissive deps already in the tree (gRPC, OTel, …). go-oidc is the canonical Go OIDC library (used by Kubernetes, Dex).

Happy to adjust naming/structure to taste — keen to get this landed for the folks in #3052.

🤖 AI Disclosure
  • I acknowledge that an LLM was used in the creation of this Pull Request, in accordance with Hatchet's AI_POLICY.md.

@vercel

vercel Bot commented Jun 13, 2026

Copy link
Copy Markdown

@mulaw is attempting to deploy a commit to the Hatchet Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added engine Related to the core Hatchet engine dashboard Related to the Hatchet dashboard documentation Improvements or additions to documentation labels Jun 13, 2026
@gregfurman

Copy link
Copy Markdown
Collaborator

Hey @awapf. Can you include an AI disclosure section to the PR body? See https://github.com/hatchet-dev/hatchet/blob/main/CONTRIBUTING.md#ai-usage

@awapf
awapf force-pushed the oidc-pr branch 2 times, most recently from df6023c to 80bfae2 Compare June 13, 2026 14:05
@awapf

awapf commented Jun 13, 2026

Copy link
Copy Markdown
Author

Hey @awapf. Can you include an AI disclosure section to the PR body? See https://github.com/hatchet-dev/hatchet/blob/main/CONTRIBUTING.md#ai-usage

Thanks for the pointer @gregfurman — I've added the 🤖 AI Disclosure to the description. Apologies for not reading CONTRIBUTING.md closely before opening this.

On that note, I also see PRs are meant to reference an issue labeled accepted, and #3052 is currently enhancement. Would you or @abelanger5 be open to marking #3052 accepted? It's the OIDC tracking issue, there's been recurring demand, and @abelanger5 mentioned on Discord that OIDC should be a small lift on top of the existing OAuth. Happy to follow whatever process you'd prefer — including discussing the approach on the issue first if that's better.

yaswanthkumar1995 and others added 5 commits June 13, 2026 16:18
Add native OIDC support for self-hosted instances, enabling direct
integration with providers like Keycloak, PocketID, and any
OpenID Connect compliant identity provider.

Changes:
- Add OIDC OAuth2 start and callback handlers
- Add OIDC config struct with env var bindings (SERVER_AUTH_OIDC_*)
- Use OIDC discovery to resolve auth/token endpoints at startup
- Cache oidc.Provider for ID token verification (no per-request discovery)
- Add OIDC routes to OpenAPI spec and regenerate server code
- Expose 'oidc' scheme via /api/v1/meta endpoint
- Add SSO button to frontend auth page
- Support domain restrictions for OIDC users
- Default scopes: openid, profile, email

Closes hatchet-dev#3052
- Add nil guards for OIDCOAuthConfig/OIDCProvider in both handlers to
  prevent nil pointer panic when OIDC is disabled
- Move AllowSignup check from start handler to CreateUser path so
  existing OIDC users can still log in when signup is disabled
- Require email_verified before authorizing or linking by email
- Add UserInfo endpoint fallback when ID token lacks optional claims
  (email, name, email_verified)
- Validate UserInfo sub claim matches ID token sub per OIDC spec
- Normalize BaseURL with TrimRight to prevent double-slash redirect URIs
- Add 30s timeout context for OIDC discovery at startup
- Enforce 'openid' scope is always present in configured scopes
- Add ScopesString field for reliable env var binding and parse it
  before building OIDCOAuthConfig so SERVER_AUTH_OIDC_SCOPES works
- declare UserUpdateOidcOauthStart/Callback in the authz rbac.yaml public
  list (alongside the Google/GitHub OAuth routes); the API refuses to start
  otherwise
- allow "oidc" in the OAuthOpts.Provider validator so the user-create path
  succeeds after a verified login
- regenerate the OpenAPI server bindings for the new routes
PR hatchet-dev#3607 shipped without tests. An in-process mock OIDC issuer (discovery +
JWKS + signed ID tokens) lets the OIDC paths be exercised without an external
IdP:
- unit: getOIDCClaimsFromToken accepts a valid token and rejects a wrong
  audience / missing id_token
- integration: upsertOIDCUserFromToken creates then updates a user against a
  real database (the path that requires provider="oidc")
Add the OIDC provider variables to the self-hosting configuration-options
page, alongside the existing Google/GitHub auth config, plus a note on issuer
discovery and the callback redirect URI.
… OIDC emails

Don't hard-reject an unverified email in the OIDC callback (the Google/GitHub
handlers don't either). Store emailVerified = claims.EmailVerified ||
SERVER_AUTH_SET_EMAIL_VERIFIED, reusing the existing instance-wide setting, so
providers that don't emit email_verified (e.g. Microsoft Entra ID) are handled
by the dashboard's verify-email gate unless SetEmailVerified is enabled.
@awapf

awapf commented Jun 13, 2026

Copy link
Copy Markdown
Author

Based on testing against Microsoft Entra ID (#2648), I changed how email verification is handled. Rather than rejecting logins without a verified email, the OIDC callback now creates the user and stores emailVerified = claims.EmailVerified || SetEmailVerified — consistent with the existing Google/GitHub handlers, and reusing the instance-wide SERVER_AUTH_SET_EMAIL_VERIFIED instead of a new OIDC-specific flag.

So providers that emit email_verified (Google, Keycloak) work as before, and providers that don't (Entra ID, which has no email_verified claim) work too — auto-verified when SET_EMAIL_VERIFIED=true, or gated behind the verify-email screen for admin approval when false.

Validated end-to-end against a real Entra tenant in both modes. This also closes #2648.

awapf and others added 2 commits June 15, 2026 20:19
@awapf

awapf commented Jul 27, 2026

Copy link
Copy Markdown
Author

Rebased onto latest main (was 219 commits behind). The only merge conflict was the generated api/v1/server/oas/gen/openapi.gen.go — resolved by re-running hack/oas/generate-server.sh; every hand-written file merged cleanly. Green and ready for review.
Re: #4428 (Azure AD / Entra ID SSO). Heads-up that these two overlap heavily — 12 of the same files — so they'll conflict with each other. They solve the same problem at different scopes:
This PR is a generic OIDC provider. It works with any OIDC-compliant IdP via discovery (Keycloak, Authentik, Dex, Okta, and Entra ID, which I've tested), and it does full cryptographic ID-token verification via JWKS with a UserInfo fallback + sub match.
#4428 is Azure-specific, built on x/oauth2 with no new dependency, and reads identity from the Microsoft userinfo endpoint (no ID-token signature verification). Its genuinely nice extras are a dedicated Azure button/icon and a tenantID knob to lock sign-in to a single directory.
Functionally #4428 is close to a subset of this PR, with the stronger security posture here (JWKS vs userinfo-only). Suggested path: land the generic provider, then layer #4428's Azure-branded button + optional single-tenant convenience on top, rather than shipping two overlapping auth stacks. Happy to do that follow-up. cc @kent-hydra — curious whether the tenantID single-directory lock is a hard requirement for your use case, since that's the main thing not yet covered here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dashboard Related to the Hatchet dashboard documentation Improvements or additions to documentation engine Related to the core Hatchet engine

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] OIDC Support

4 participants