Skip to content

feat: native OIDC client for per-user SSO (Authentik, Keycloak, any OIDC provider)#1561

Open
aaronckj wants to merge 1 commit intolinuxserver:2.xfrom
aaronckj:authentik-oidc
Open

feat: native OIDC client for per-user SSO (Authentik, Keycloak, any OIDC provider)#1561
aaronckj wants to merge 1 commit intolinuxserver:2.xfrom
aaronckj:authentik-oidc

Conversation

@aaronckj
Copy link
Copy Markdown

@aaronckj aaronckj commented May 5, 2026

Summary

Adds a native OpenID Connect (OIDC) client to Heimdall so each user can sign in once via an external identity provider (Authentik, Keycloak, Dex, etc.) and land directly on their own dashboard — without a second Heimdall login and without needing a reverse-proxy forward-auth layer.

Closes / relates to: #1445 (request for per-user dashboards with AD/LDAP), and the pattern requested in many forum threads asking for SSO beyond forward-auth headers.

What it does

  • One login, per-user dashboard. OIDC preferred_username is mapped to a Heimdall username. The user is found or auto-provisioned, then logged in via Auth::login().
  • Admin break-glass preserved. A configurable username (default: admin) is hard-blocked from OIDC — case-insensitively, even after username mapping. The local password form remains accessible on /login below the SSO button.
  • No proxy dependency. Works standalone — no Caddy/nginx forward-auth required (though it's compatible with one).
  • Username mapping. OIDC_USERNAME_MAP=src:dst,src2:dst2 handles providers that send a different preferred_username than the Heimdall username (e.g. an admin account with a different name).
  • User switcher hidden when OIDC active. The Switch User button and /userselect route redirect to the authenticated user's own dashboard — users can't casually browse other people's dashboards.
  • Fully opt-in. OIDC_ENABLED=false (the default) means zero behaviour change for existing installations.

Files changed

File Change
app/Services/OidcUserRepoContract.php New — repo interface
app/Services/OidcUserResolver.php New — username mapping + break-glass guard + provision logic
app/Services/EloquentOidcUserRepo.php New — Eloquent implementation of the repo
app/Http/Controllers/Auth/OidcController.php New — GET /auth/oidc/login + GET /auth/oidc/callback
app/Http/Controllers/Auth/LoginController.php Modified — show login view directly (Authentik button at top, form below)
app/Http/Controllers/UserController.php Modified — bypass /userselect when OIDC + authenticated
config/services.php Modified — oidc config block, all values from env
routes/web.php Modified — register two new OIDC routes
resources/views/auth/login.blade.php Modified — "Sign in with Authentik" button when OIDC enabled
resources/views/layouts/app.blade.php Modified — hide Switch User button when OIDC enabled
tests/Unit/OidcUserResolverTest.php New — 8 unit tests covering all resolver branches
Dockerfile New — builds the image with jumbojett/openid-connect-php added

Dependencies

One new Composer dependency: jumbojett/openid-connect-php (^1.0). This is a widely-used, actively maintained OIDC client with no framework coupling. The Dockerfile handles the composer require step; for a manual install add it to composer.json.

Also requires ext-sodium (for OIDC token crypto) — not present in the linuxserver Alpine image by default. The Dockerfile installs php84-sodium via apk.

Configuration

OIDC_ENABLED=true
OIDC_ISSUER=https://auth.example.com/application/o/heimdall/
OIDC_CLIENT_ID=<client-id>
OIDC_CLIENT_SECRET=<client-secret>
OIDC_REDIRECT_URI=https://heimdall.example.com/auth/oidc/callback
OIDC_AUTO_PROVISION=true
OIDC_ADMIN_BREAKGLASS_USERNAME=admin
OIDC_USERNAME_MAP=                  # optional: "akadmin:aaronckj,other:alias"
OIDC_SCOPES=openid,email,profile

Full setup guide in AUTHENTIK_OIDC.md (works with any OIDC provider, not just Authentik).

Testing

  • Tested against Authentik 2026.2.2, PHP 8.4.16 (Alpine 3.22), Laravel 11.45.
  • 8 unit tests in tests/Unit/OidcUserResolverTest.php cover: username mapping, identity passthrough, admin break-glass (direct + post-mapping + case-insensitive), existing user no-mutate, auto-provision enabled/disabled.
  • End-to-end tested with 4 users (including auto-provisioning a new user on first login and verifying existing users land on their correct dashboards).
  • OIDC_ENABLED=false (default) was verified to be a no-op — existing installations unaffected.

Caveats / known limitations

  • jumbojett/openid-connect-php uses PHP's native $_SESSION for state/nonce storage. Heimdall's file session driver works correctly alongside it, but this should be noted in documentation for users on other session drivers.
  • The Dockerfile pins to lscr.io/linuxserver/heimdall:v2.7.6-ls341 as the base — maintainers may want to adjust the build pipeline to produce an official variant instead.
  • Username mapping is static (env var). Dynamic mapping (e.g., from OIDC groups) would require an additional hook — out of scope for this PR.

- OidcController: authorization-code flow via jumbojett/openid-connect-php
- OidcUserResolver: username mapping, case-insensitive admin break-glass guard,
  auto-provisioning — unit tested (8 assertions)
- EloquentOidcUserRepo: find-or-create Heimdall user from OIDC claims
- LoginController: show login page directly; Authentik button at top,
  local admin form below — no magic URL required
- UserController: /userselect redirects to dash when OIDC active + authenticated
- layouts/app.blade.php: hide Switch User button when OIDC active
- config/services.php: oidc config block (all values from env)
- routes/web.php: GET /auth/oidc/login and /auth/oidc/callback
- Dockerfile: layer overlay + composer require + php84-sodium onto upstream image
- AUTHENTIK_OIDC.md: setup guide and env var reference

Tested against Authentik 2026.2.2, PHP 8.4 (Alpine), Laravel 11.45.
Upstream pinned to v2.7.6 / lscr.io tag v2.7.6-ls341.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

2 participants