docs(auth): document APP_URL requirement for reverse-proxied deployments #99

Closed
opened 2026-06-18 03:32:39 +00:00 by james · 0 comments
Owner

Problem

When Carol runs behind a reverse proxy (the common self-hoster shape — Caddy / Traefik / nginx / Cloudflare in front of the container on 0.0.0.0:3000), OAuth and OIDC sign-in flows produce a redirect_uri of http://0.0.0.0:3000/... instead of the public URL. The IdP then either refuses the request (redirect_uri doesn't match what's registered) or completes consent and tries to redirect the browser to an unreachable internal URL.

The mechanism is appOrigin() in /api/auth/oauth/start and /api/auth/oauth/callback/[provider]:

function appOrigin(req: NextRequest): string {
  return process.env.APP_URL ?? req.nextUrl.origin;
}

req.nextUrl.origin reflects the bind address inside the container. APP_URL is the escape hatch. This is documented as a one-liner in ADR-0015 §2 (Redirect-URI) but isn't called out in any self-hoster-facing doc, so the first thing a self-hoster encounters is "OIDC didn't work" with a confusing 0.0.0.0:3000 callback URL.

Hit while deploying #85 (OIDC) to carol.int.wynning.tech.

Scope

Pure docs. No code change.

  • docs/oidc-self-hoster-guide.md: add an APP_URL prerequisite paragraph at the top before the recipes. Spell out the exact format (https://<your-host>, no trailing slash) and that it must match what's registered as the callback URL prefix in the IdP.
  • docs/oidc-self-hoster-guide.md "Troubleshooting": add an entry for "Sign-in redirects to http://0.0.0.0:3000" → set APP_URL.
  • README.md deployment section (if one exists) or CLAUDE.md "Working in this repo" — short note that APP_URL is required for any deployment behind a reverse proxy. Cross-reference the self-hoster guide.
  • Confirm GitHub OAuth has the same requirement (it does — same appOrigin() helper) and mention it in the prerequisite paragraph so the self-hoster knows it applies to both GitHub and OIDC.

Out of scope

Auto-deriving the public URL from X-Forwarded-Proto + X-Forwarded-Host / Forwarded headers. That's a defensible code change (would remove the env-var-required failure mode for the standard reverse-proxy case) but it widens the trust surface — the proxy must be configured to strip incoming X-Forwarded-* from external requests, otherwise an attacker can forge a redirect_uri. If we revisit this it deserves its own ticket + ADR amendment to ADR-0015 §2.

## Problem When Carol runs behind a reverse proxy (the common self-hoster shape — Caddy / Traefik / nginx / Cloudflare in front of the container on `0.0.0.0:3000`), OAuth and OIDC sign-in flows produce a `redirect_uri` of `http://0.0.0.0:3000/...` instead of the public URL. The IdP then either refuses the request (redirect_uri doesn't match what's registered) or completes consent and tries to redirect the browser to an unreachable internal URL. The mechanism is `appOrigin()` in `/api/auth/oauth/start` and `/api/auth/oauth/callback/[provider]`: ```ts function appOrigin(req: NextRequest): string { return process.env.APP_URL ?? req.nextUrl.origin; } ``` `req.nextUrl.origin` reflects the bind address inside the container. `APP_URL` is the escape hatch. This is documented as a one-liner in ADR-0015 §2 (Redirect-URI) but isn't called out in any self-hoster-facing doc, so the first thing a self-hoster encounters is "OIDC didn't work" with a confusing `0.0.0.0:3000` callback URL. Hit while deploying #85 (OIDC) to `carol.int.wynning.tech`. ## Scope Pure docs. No code change. - [ ] `docs/oidc-self-hoster-guide.md`: add an `APP_URL` prerequisite paragraph at the top before the recipes. Spell out the exact format (`https://<your-host>`, no trailing slash) and that it must match what's registered as the callback URL prefix in the IdP. - [ ] `docs/oidc-self-hoster-guide.md` "Troubleshooting": add an entry for "Sign-in redirects to `http://0.0.0.0:3000`" → set `APP_URL`. - [ ] `README.md` deployment section (if one exists) or `CLAUDE.md` "Working in this repo" — short note that `APP_URL` is required for any deployment behind a reverse proxy. Cross-reference the self-hoster guide. - [ ] Confirm GitHub OAuth has the same requirement (it does — same `appOrigin()` helper) and mention it in the prerequisite paragraph so the self-hoster knows it applies to both GitHub and OIDC. ## Out of scope Auto-deriving the public URL from `X-Forwarded-Proto` + `X-Forwarded-Host` / `Forwarded` headers. That's a defensible code change (would remove the env-var-required failure mode for the standard reverse-proxy case) but it widens the trust surface — the proxy must be configured to strip incoming `X-Forwarded-*` from external requests, otherwise an attacker can forge a `redirect_uri`. If we revisit this it deserves its own ticket + ADR amendment to ADR-0015 §2.
james closed this issue 2026-06-19 12:22:47 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
james/carol#99
No description provided.