ForwardAuth vs OIDC: Choosing the Right Auth Pattern Per App
Not every app authenticates the same way. After configuring 15 services with Authentik, I landed on three patterns: ForwardAuth, native OIDC, and built-in auth. Here is the decision matrix.
When I set up Authentik as the identity provider for my homelab, I assumed every app would use the same authentication pattern. It took about three apps to realize that's not how it works. Some apps support OIDC natively. Some support proxy headers. Some support nothing and need a gate in front of them.
After configuring authentication for 15 services, I've landed on three distinct patterns, each chosen for specific reasons. Here's the decision matrix.
The Three Patterns
Pattern 1: ForwardAuth (Traefik Middleware)
Traefik's ForwardAuth middleware intercepts every request and sends it to Authentik's outpost before passing it to the backend. If the user isn't authenticated, they're redirected to the Authentik login page. If they are, Traefik forwards the request with additional headers containing the user's identity.
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: authentik-forward-auth
spec:
forwardAuth:
address: http://authentik-outpost.authentik.svc.cluster.local:9000/outpost.goauthentik.io/auth/traefik
trustForwardHeader: true
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
- X-authentik-email
- X-authentik-name
- X-authentik-uidThe app never sees the authentication flow. It receives pre-authenticated requests with identity headers. This is the most transparent pattern: the app doesn't need to know Authentik exists.
Pattern 2: Native OIDC
Some apps have built-in OIDC/OAuth2 support. They redirect to Authentik themselves, receive an authorization code, exchange it for tokens, and manage their own sessions. The app handles authentication end-to-end.
# Example: Miniflux OIDC configuration
env:
- name: OAUTH2_PROVIDER
value: oidc
- name: OAUTH2_OIDC_DISCOVERY_URL
value: https://auth.naviauxlab.com/application/o/miniflux/.well-known/openid-configuration
- name: OAUTH2_CLIENT_ID
valueFrom:
secretKeyRef:
name: miniflux-oidc
key: client-id
- name: OAUTH2_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: miniflux-oidc
key: client-secret
- name: OAUTH2_REDIRECT_URL
value: https://miniflux.naviauxlab.com/oauth2/oidc/callbackPattern 3: Built-in Auth Only
Some apps don't support external authentication at all, or their built-in auth is sufficient. These manage their own user database and session handling.
The Decision Matrix
| App | Auth Pattern | Why |
|---|---|---|
| Homepage | ForwardAuth | Static dashboard, no built-in auth |
| Grafana | ForwardAuth | Supports OIDC but ForwardAuth is simpler for single-user |
| Longhorn UI | ForwardAuth | No built-in auth, needs protection |
| Uptime Kuma | ForwardAuth | Has built-in auth but ForwardAuth gives SSO |
| Life Hub | ForwardAuth + API Key | UI via ForwardAuth, API via Bearer token |
| Actual Budget | ForwardAuth | Has password auth but no OIDC support |
| Mealie | ForwardAuth | Has OIDC but implementation is buggy |
| Miniflux | Native OIDC | Excellent OIDC implementation, auto-creates users |
| Linkding | Native OIDC | Good OIDC support with auto-provisioning |
| Navidrome | ForwardAuth + Proxy Header | Reads X-authentik-username, auto-creates Navidrome users |
| Audiobookshelf | Built-in | No OIDC, has its own user management |
| Ghost | Built-in (Admin) | Ghost admin uses its own auth; public site is open |
| Vaultwarden | Built-in | Password manager must handle its own auth |
| ntfy | Built-in | Push notifications need direct token auth |
When to Use ForwardAuth
ForwardAuth is the default choice. Use it when:
- The app has no built-in auth. Homepage, Longhorn UI, and similar admin dashboards have no login page. ForwardAuth adds one without modifying the app.
- The app's OIDC implementation is incomplete. Mealie technically supports OIDC, but the implementation had bugs with token refresh that caused random logouts. ForwardAuth bypasses the problem entirely.
- You want SSO without per-app configuration. ForwardAuth is configured once as a Traefik middleware and applied to any IngressRoute. Adding auth to a new app is one line in the route spec.
- The app reads proxy headers. Navidrome's
ND_REVERSEPROXYUSERHEADERsetting auto-creates users from theX-authentik-usernameheader. ForwardAuth provides the header; Navidrome handles user provisioning.
The tradeoff: ForwardAuth doesn't give the app an identity token. The app knows who the user is (via headers) but can't make API calls on behalf of the user to other OIDC-protected services. For most homelab apps, this doesn't matter.
When to Use Native OIDC
Use OIDC when the app has a mature implementation and benefits from direct token management:
- Miniflux. The OIDC implementation is clean: auto-creates users on first login, maps OIDC claims to Miniflux user attributes, handles token refresh correctly. No reason to put ForwardAuth in front of it.
- Linkding. Similar story: OIDC works, user provisioning is automatic, sessions are managed properly.
- Grafana (if you need teams/RBAC). In a multi-user setup, Grafana's OIDC integration maps OIDC groups to Grafana organizations and roles. ForwardAuth can't do this because it only passes headers, not group membership claims with role mappings.
The tradeoff: Each OIDC app requires its own Authentik Application and Provider configuration: client ID, client secret, redirect URI, scopes. More setup per app, but richer integration.
When to Use Built-in Auth
Some apps should handle their own authentication regardless of what your identity provider supports:
- Vaultwarden. Your password manager is the last line of defense. If Authentik goes down, you still need access to your passwords. Putting Vaultwarden behind ForwardAuth would mean an Authentik outage locks you out of every credential. Built-in auth with a strong master password is the correct choice.
- Ghost Admin. Ghost's admin panel has its own auth flow that manages editor roles, contributor permissions, and API keys. OIDC would replace this with a flat "authenticated" state that loses the role granularity. The public-facing blog is unauthenticated.
- ntfy. Push notification subscriptions use bearer tokens for both publishing and subscribing. Mobile apps send these tokens directly. Putting ForwardAuth in front of ntfy would break every mobile client.
The Hybrid Pattern: Life Hub
Life Hub uses both ForwardAuth and API key authentication, for different interfaces:
- HTMX UI → ForwardAuth via Traefik. Users hit the web interface, Authentik handles login, the UI loads with the API key injected via a server-rendered
<meta>tag. - REST API → API key via
Authorization: BearerorX-API-Keyheader. Collectors, CronJobs, and external clients authenticate directly. - MCP Server → Bearer token via custom ASGI middleware. Claude Code connects directly, bypassing Traefik's ForwardAuth.
The MCP endpoint (/mcp) has its own IngressRoute that skips the Authentik middleware. This is necessary because the MCP protocol doesn't support browser-based OAuth redirects. It sends a bearer token in the initial HTTP request.
Authentik Configuration Tips
One Application per service, even for ForwardAuth. Even though ForwardAuth apps don't use client IDs and secrets, creating an Authentik Application per service lets you control access with per-app authorization policies. Want to restrict Longhorn UI to admin users? Add a group-based policy to that Application.
Use the embedded outpost. Authentik can run its proxy outpost as a separate deployment or as an embedded component. For homelabs, the embedded outpost is simpler: one fewer pod to manage, and it scales with the Authentik server.
Cache the OIDC discovery document. Miniflux and Linkding fetch .well-known/openid-configuration on startup. If Authentik is slow to start, the app may fail its initial OIDC setup. Add readiness dependencies or startup retry logic.
ForwardAuth headers are trusted by convention. The X-authentik-username header is only secure because Traefik strips it from incoming requests and only sets it after ForwardAuth succeeds. If an attacker could bypass Traefik and hit the app directly with a forged header, they'd be authenticated as anyone. Network policies that restrict ingress to Traefik's pod are essential.
The Redirect Loop Problem
The most common failure mode with ForwardAuth is the redirect loop: Authentik redirects to the app, the app's request hits ForwardAuth again, Authentik says "not authenticated" (because the session cookie is for a different domain), and redirects again. Infinite loop.
The fix is almost always one of:
- Cookie domain mismatch. Authentik's session cookie must be set for a domain that covers both the Authentik host and the app host. If Authentik is at
auth.naviauxlab.comand the app is atapp.naviauxlab.com, the cookie domain should be.naviauxlab.com. - Missing
X-Forwarded-Proto: https. If Traefik terminates TLS but passes the request to Authentik as HTTP, the redirect URL will be HTTP, which mismatches the browser's HTTPS URL. Ensure the ForwardAuth request includes the original protocol. - Embedded outpost not matching the external URL. The outpost needs to know the external URL of the application to set the correct redirect. Misconfigure this and you get redirects to
localhost:9000.
I've hit all three. The redirect loop debugging experience is what prompted me to write the dedicated troubleshooting post earlier in this series.
Lessons Learned
ForwardAuth is the 80% solution. Most homelab apps don't need rich OIDC integration. They need "is this person allowed to access this?" ForwardAuth answers that question with minimal per-app configuration.
Don't force OIDC where built-in auth is correct. Vaultwarden and ntfy are better with their own auth. Fighting against an app's authentication model creates brittleness without adding security.
Network policies enforce the trust boundary. ForwardAuth headers are only trustworthy if apps can't be reached without going through Traefik. Network policies are not optional when using proxy-based authentication.
Test each pattern independently. Before wiring up ForwardAuth, verify that Authentik's Application and outpost configuration works by hitting the outpost URL directly. Before configuring OIDC, verify the discovery URL resolves and the client credentials work with a manual token exchange. Debug one layer at a time.
The right auth pattern isn't the most sophisticated one. It's the one that matches what the app actually supports, maintains security boundaries you can reason about, and doesn't require you to debug redirect loops at midnight.