feat(auth): humans sign in with OIDC; no header names a user #14

Merged
k.renner merged 2 commits from feat/oidc-login into main 2026-10-19 10:13:28 +00:00
Member

Merge after konairius/homelab#1382. That PR supplies Auth__Oidc__*. Without them this image refuses to start, and Flux rolls it out as soon as it's built.

Problem: the ProxyHeader scheme trusted X-Authentik-Username from Auth:ReverseProxy:TrustedProxies, which the deployment set to the whole pod CIDR. Any pod allowed to reach :8080 (Hermes, Gitea, Traefik, everything in the ai and monitoring namespaces) could claim any user's name on the Facts page and on saved answers. Authentik's proxy provider sends no signed identity header, so the header can't be verified.

Change

  • Humans sign in with OIDC against Authentik: public client with PKCE. The username is preferred_username from the ID token, and the token's signature is checked against the provider's JWKS. No IP address or request header grants an identity any more.
  • Auth:Oidc:Authority and Auth:Oidc:ClientId are required. The redirect URI is https://<request host>/signin-oidc, with https because TLS ends at Traefik.
  • UI pages, static assets and the Blazor hub need a session. Anonymous browsers are redirected to Authentik.
  • Read API (/api/v1 reads, /ask, /questions, /rank, /status) needs a session or an API key. Without one it returns 401, never a redirect. Before this it was anonymous and relied on whatever sat in front: the gate, BasicAuth or network policy.
  • Writes still need an API key. /health, /ready and the HMAC-signed /webhook stay anonymous.
  • ProxyHeaderAuthenticationHandler and Auth:ReverseProxy are removed. New dependency: Microsoft.AspNetCore.Authentication.OpenIdConnect 10.0.13.

Tests: dotnet test passes locally, 145/145. A fake in-process identity provider issues RS256 ID tokens. The tests cover:

  • the PKCE challenge, and the redirect URI behind TLS termination;
  • the full code flow ending in a session for the token's user (captured_by: alice);
  • a token signed by a foreign key is rejected (IDX10511);
  • X-Authentik-Username signs nobody in;
  • API calls without credentials get 401.

Watch: the read API on wiki-index.renner.uno now needs X-Api-Key in addition to BasicAuth. Check that bin/wiki_rank.py and wiki_ask.py on the workstation send the workstation key. I can't see those scripts. Hermes already sends its key.

**Merge after konairius/homelab#1382.** That PR supplies `Auth__Oidc__*`. Without them this image refuses to start, and Flux rolls it out as soon as it's built. **Problem:** the ProxyHeader scheme trusted `X-Authentik-Username` from `Auth:ReverseProxy:TrustedProxies`, which the deployment set to the whole pod CIDR. Any pod allowed to reach :8080 (Hermes, Gitea, Traefik, everything in the ai and monitoring namespaces) could claim any user's name on the Facts page and on saved answers. Authentik's proxy provider sends no signed identity header, so the header can't be verified. **Change** - Humans sign in with OIDC against Authentik: public client with PKCE. The username is `preferred_username` from the ID token, and the token's signature is checked against the provider's JWKS. No IP address or request header grants an identity any more. - `Auth:Oidc:Authority` and `Auth:Oidc:ClientId` are required. The redirect URI is `https://<request host>/signin-oidc`, with https because TLS ends at Traefik. - **UI pages, static assets and the Blazor hub** need a session. Anonymous browsers are redirected to Authentik. - **Read API** (`/api/v1` reads, `/ask`, `/questions`, `/rank`, `/status`) needs a session or an API key. Without one it returns 401, never a redirect. Before this it was anonymous and relied on whatever sat in front: the gate, BasicAuth or network policy. - **Writes** still need an API key. **`/health`, `/ready` and the HMAC-signed `/webhook`** stay anonymous. - `ProxyHeaderAuthenticationHandler` and `Auth:ReverseProxy` are removed. New dependency: `Microsoft.AspNetCore.Authentication.OpenIdConnect` 10.0.13. **Tests:** `dotnet test` passes locally, 145/145. A fake in-process identity provider issues RS256 ID tokens. The tests cover: - the PKCE challenge, and the redirect URI behind TLS termination; - the full code flow ending in a session for the token's user (`captured_by: alice`); - a token signed by a foreign key is rejected (`IDX10511`); - `X-Authentik-Username` signs nobody in; - API calls without credentials get 401. **Watch:** the read API on wiki-index.renner.uno now needs `X-Api-Key` in addition to BasicAuth. Check that bin/wiki_rank.py and wiki_ask.py on the workstation send the `workstation` key. I can't see those scripts. Hermes already sends its key.
hermes-bot added 2 commits 2026-10-19 09:47:56 +00:00
feat(auth): humans sign in with OIDC; no header names a user
ci/woodpecker/push/test Pipeline was successful
0ecf1aaac5
The ProxyHeader scheme took the username from X-Authentik-Username for any
peer inside Auth:ReverseProxy:TrustedProxies. The deployment set that to
the whole pod CIDR (a hand copy of the Talos podSubnets), so every pod the
network policies let reach :8080 (Hermes, Gitea, Traefik, all of ai and
monitoring) could claim any human's name on the Facts page and on saved
answers. Authentik's proxy provider sends no signed identity header, so the
header cannot be verified at all.

Humans now sign in with OpenID Connect against Authentik: a public client
with PKCE, the username is preferred_username from the ID token, whose
signature is checked against the provider's JWKS. Nothing about the
network path or the request headers names a user any more.

- Auth:Oidc:Authority, ClientId and PublicOrigin are required; the host
  refuses to start without them. PublicOrigin builds the redirect URI,
  because TLS ends at Traefik and the request scheme is http.
- UI pages, static assets and the Blazor hub need a session (fallback
  policy); an anonymous browser is redirected to the provider.
- The read API (/api/v1 reads, /ask, /questions, /rank, /status) needs a
  session or an API key (policy "read") and answers 401, never a redirect.
  Before, it was anonymous and relied on the gate, BasicAuth or network
  policy in front.
- Writes still need an API key (policy "write").
- /health, /ready and the HMAC-signed /webhook stay anonymous.
- ProxyHeaderAuthenticationHandler and Auth:ReverseProxy are removed.

Tests: an in-process OpenID provider issues RS256 ID tokens. They cover
the PKCE challenge and redirect URI, the full code flow ending in a
session named after the token's user, rejection of a token signed by a
foreign key, the Authentik header signing in nobody, and 401s for
credential-less API calls.
fix(auth): redirect URI from the request host, no PublicOrigin setting
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
4820bc9f6d
Auth:Oidc:PublicOrigin was a hand copy of the Ingress host. Traefik routes on
the Host header and Authentik matches redirect URIs strictly, so the URI is
https://<request host>/signin-oidc; https because TLS ends at Traefik and
browsers only reach the wiki over https.
k.renner merged commit 311f59d82a into main 2026-10-19 10:13:28 +00:00
k.renner deleted branch feat/oidc-login 2026-10-19 10:13:29 +00:00
Sign in to join this conversation.