fix(caddy,authentik): auth guarded vhosts by proxying the whole request through authentik
The forward_auth target /outpost.goauthentik.io/auth/caddy does not exist in authentik 2026.8 (verified live: 404 for every header combination; the URL tree has no outpost auth prefix at all — the outpost worker dials back to core over ws/outpost/<uuid>/ instead). Unauthenticated subrequests got 404, Caddy treated >=400 as 'not authenticated' and redirected browsers to the dead path — users saw authentik's own Not-Found page. Fix: guarded vhosts now proxy the whole request through authentik-server:9000 with X-authentik-meta-outpost/-provider/-app headers (the exact header set this build's Traefik controller emits, adapted for Caddy). Unauthenticated clients get the login flow served in place; once a session exists, authentik proxies on to the provider's internal_host — now http://caddy:8017, a new post-auth internal listener that routes straight to the app. The chain terminates; nothing dials back into authentik. Live DB changes applied alongside (postgres is the source of truth): provider pk=1 authentication_flow=default-authentication-flow (was NULL — the actual cause of the 404s) and internal_host=http://caddy:8017 (was self-referential http://authentik-server:9000).
This commit is contained in:
@@ -24,41 +24,27 @@ so the stack is 3 services, not 4.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph Browser
|
||||
U[User]
|
||||
end
|
||||
subgraph Caddy [Caddy - auth gate]
|
||||
FA[forward_auth]
|
||||
end
|
||||
subgraph AK [Authentik]
|
||||
SRV[server :9000]
|
||||
WKR[worker]
|
||||
PG[(postgres)]
|
||||
OUT[outpost /auth/caddy]
|
||||
end
|
||||
subgraph App [Target app]
|
||||
A1[SearXNG / Hermes / Prometheus<br/>RomM / Copyparty / homepage]
|
||||
end
|
||||
|
||||
U -->|1. GET /| FA
|
||||
FA -->|2. GET /outpost.../auth/caddy| OUT
|
||||
OUT -->|3. no session: 302 login| U
|
||||
U -->|4. login at auth.franksdank.cloud| SRV
|
||||
SRV --> PG
|
||||
WKR --> PG
|
||||
SRV -->|5. 2xx: session valid| FA
|
||||
FA -->|6. proxy with headers| A1
|
||||
U[Browser] -->|1. GET /search?q=x| A[Anubis :8923<br/>bot challenge]
|
||||
A -->|2. verified, dials internal listener| C[Caddy :8016<br/>auth decision]
|
||||
C -->|3. whole request + meta headers| S[authentik-server :9000<br/>proxy provider 1]
|
||||
S -->|4. no session: login flow served in place| U
|
||||
S -->|5. session valid: proxies on to internal_host| P[Caddy :8017<br/>post-auth routing]
|
||||
P -->|6. app response back up the chain| U
|
||||
S --> PG[(postgres)]
|
||||
```
|
||||
|
||||
1. The browser hits a protected vhost (e.g. `search.franksdank.cloud`).
|
||||
2. Caddy `forward_auth` clones the request to the Authentik outpost
|
||||
(`/outpost.goauthentik.io/auth/caddy`).
|
||||
3. With no valid session, the outpost returns a `302` to the Authentik login
|
||||
page (`auth.franksdank.cloud`).
|
||||
4. The user logs in at Authentik (email + password, TOTP/WebAuthn MFA, or an
|
||||
upstream provider).
|
||||
5. On the next request the outpost returns `2xx`; Caddy copies the
|
||||
`X-Authentik-*` headers and proxies to the app.
|
||||
In authentik 2026.8 the proxy provider is an OAuth2 provider and the old
|
||||
`/outpost.goauthentik.io/auth/caddy` forward-auth endpoint no longer exists —
|
||||
every request that reaches it returns 404 (verified live, 2026-08-29). The
|
||||
chain therefore does not forward-auth *to* a path: guarded vhosts proxy the
|
||||
**whole request through authentik** with `X-authentik-meta-outpost`,
|
||||
`X-authentik-meta-provider` and `X-authentik-meta-app` headers. The provider's
|
||||
`authentication_flow` (`default-authentication-flow`) decides: no session →
|
||||
the login flow is served in place; session valid → the policy passes and
|
||||
authentik proxies the request on to the provider's `internal_host`
|
||||
(`http://caddy:8017`, Caddy's post-auth internal listener), which routes it
|
||||
straight to the app. The chain terminates — nothing dials back into
|
||||
Authentik from `:8017`.
|
||||
|
||||
## The three compose services
|
||||
|
||||
@@ -79,30 +65,39 @@ graph LR
|
||||
|
||||
## Caddy integration
|
||||
|
||||
The Caddyfile wraps the no-auth vhosts in a `route` block. The pattern (one
|
||||
example; the others are identical):
|
||||
Guarded vhosts proxy the **whole request through Authentik** on Caddy's
|
||||
internal listener — there is no `forward_auth` anywhere: in 2026.8 the auth
|
||||
endpoint the old config pointed at (`/outpost.goauthentik.io/auth/caddy`)
|
||||
does not exist in the URL tree, so every unauthenticated subrequest got a
|
||||
404 and browsers were served authentik's own Not-Found page (verified live,
|
||||
2026-08-29). Pattern (one example; the other guarded vhosts are identical):
|
||||
|
||||
```caddyfile
|
||||
search.franksdank.cloud {
|
||||
route {
|
||||
# 1. The outpost path is proxied to Authentik (login UI + session).
|
||||
reverse_proxy /outpost.goauthentik.io/* authentik-server:9000
|
||||
# 2. forward_auth checks the session before serving the app.
|
||||
forward_auth authentik-server:9000 {
|
||||
uri /outpost.goauthentik.io/auth/caddy
|
||||
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name
|
||||
trusted_proxies private_ranges
|
||||
http://:8016 {
|
||||
@search host search.franksdank.cloud
|
||||
handle @search {
|
||||
# Unauthenticated: the login flow is served in place.
|
||||
# Authenticated: Authentik proxies on to internal_host (:8017).
|
||||
reverse_proxy authentik-server:9000 {
|
||||
header_up X-authentik-meta-outpost 7e39655b-58d3-4fe6-a80a-513f31051217
|
||||
header_up X-authentik-meta-provider 1
|
||||
header_up X-authentik-meta-app name=caddy
|
||||
}
|
||||
# 3. The app.
|
||||
reverse_proxy 192.168.0.143:8081
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Vhosts protected by forward_auth: the homepage (`franksdank.cloud`,
|
||||
`www.franksdank.cloud`), SearXNG, Hermes, Prometheus, RomM, Copyparty. The
|
||||
`auth.franksdank.cloud` vhost proxies to the server directly (no forward_auth —
|
||||
it *is* the login).
|
||||
The meta headers tell authentik which outpost, provider and application are
|
||||
answering (the values come from the DB: outpost pk = the UUID embedded in
|
||||
`AUTHENTIK_OUTPOST_TOKEN`, provider pk 1 = `embedded-outpost`, app slug
|
||||
`caddy`). The provider's `internal_host` is `http://caddy:8017`, so
|
||||
authenticated traffic is handed back to Caddy's post-auth listener, which
|
||||
routes it straight to the app — the chain terminates there.
|
||||
|
||||
Guarded vhosts: the homepage (`franksdank.cloud`, `www.franksdank.cloud`),
|
||||
SearXNG, Hermes, Prometheus, RomM, Copyparty. The `auth.franksdank.cloud`
|
||||
vhost proxies to the server directly — it *is* the login, and it must never
|
||||
sit behind a challenge.
|
||||
|
||||
## Configuration (secrets)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user