fix(auth): post-callback redirect honors APP_URL (reverse-proxy) (#102) #103

Merged
james merged 1 commit from 102-redirect-honor-app-url into main 2026-06-18 12:08:47 +00:00
Owner

Summary

The redirectTo() helper in app/api/auth/oauth/callback/[provider]/route.ts resolved redirect locations (/login, /profile, /account) against req.nextUrl raw — which inside a container behind a reverse proxy is the bind address (http://0.0.0.0:3000). Every refused-callback path 302'd users to an internal URL their browser couldn't reach.

Same class of bug as the outbound redirect_uri before APP_URL was threaded through appOrigin(): inbound IdP callback worked (uses APP_URL), but the response-time redirect didn't. Discovered while testing #100 against Authentik on carol.int.wynning.tech?error=oidc_claim correctly identified but the redirect landed on http://0.0.0.0:3000/login?error=oidc_claim instead of https://carol.int.wynning.tech/login?....

Fix: redirectTo() now builds the URL against appOrigin(req) (the same helper that drives the outbound redirect_uri). The same-origin defence-in-depth check compares against the same public origin. Single source of truth for the public URL stays APP_URL.

Test plan

  • npm run typecheck — clean.
  • npm run lint — clean.
  • npm test — 215 / 38 skipped (one new test).
  • New scenario: set APP_URL=https://carol.example.com, drive the no_verified_email refusal, assert the Location header starts with https://carol.example.com/login?error=no_verified_email.
  • Manual re-probe on carol.int.wynning.tech after a release — the oidc_claim error from #100 should now land on the public URL.

Closes #102.

🤖 Generated with Claude Code

## Summary The `redirectTo()` helper in `app/api/auth/oauth/callback/[provider]/route.ts` resolved redirect locations (`/login`, `/profile`, `/account`) against `req.nextUrl` raw — which inside a container behind a reverse proxy is the bind address (`http://0.0.0.0:3000`). Every refused-callback path 302'd users to an internal URL their browser couldn't reach. Same class of bug as the outbound `redirect_uri` before `APP_URL` was threaded through `appOrigin()`: inbound IdP callback worked (uses `APP_URL`), but the response-time redirect didn't. Discovered while testing #100 against Authentik on `carol.int.wynning.tech` — `?error=oidc_claim` correctly identified but the redirect landed on `http://0.0.0.0:3000/login?error=oidc_claim` instead of `https://carol.int.wynning.tech/login?...`. Fix: `redirectTo()` now builds the URL against `appOrigin(req)` (the same helper that drives the outbound `redirect_uri`). The same-origin defence-in-depth check compares against the same public origin. Single source of truth for the public URL stays `APP_URL`. ## Test plan - [x] `npm run typecheck` — clean. - [x] `npm run lint` — clean. - [x] `npm test` — 215 / 38 skipped (one new test). - [x] New scenario: set `APP_URL=https://carol.example.com`, drive the `no_verified_email` refusal, assert the `Location` header starts with `https://carol.example.com/login?error=no_verified_email`. - [ ] Manual re-probe on `carol.int.wynning.tech` after a release — the `oidc_claim` error from #100 should now land on the public URL. Closes #102. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(auth): post-callback redirect honors APP_URL (reverse-proxy)
Some checks failed
Commits / Conventional Commits (pull_request) Successful in 49s
Secrets / gitleaks (pull_request) Successful in 24s
PR / Static analysis (pull_request) Successful in 1m21s
PR / OSV-Scanner (pull_request) Failing after 1m27s
PR / Trivy (image) (pull_request) Successful in 1m47s
PR / Lint (pull_request) Successful in 4m37s
PR / npm audit (pull_request) Successful in 4m55s
PR / Typecheck (pull_request) Successful in 5m11s
PR / Test (sqlite) (pull_request) Successful in 5m13s
PR / Test (postgres) (pull_request) Successful in 6m47s
PR / Build (pull_request) Successful in 8m33s
c3777b9eab
The redirectTo() helper in /api/auth/oauth/callback/[provider]
resolved /login + /profile + /account redirects against req.nextUrl,
which inside the container is the bind address (0.0.0.0:3000). A
behind-reverse-proxy deployment landed users on
http://0.0.0.0:3000/login?error=... after every refused-callback path
— same class of bug as the outbound redirect_uri before APP_URL was
threaded through there.

Fix: redirectTo() now builds against appOrigin(req), which honors
APP_URL when set and falls back to req.nextUrl.origin otherwise. The
same-origin defence-in-depth check compares against the same public
origin. Single source of truth for the public URL stays APP_URL.

Discovered while testing the #100 diagnostic surfacing on a deployed
Authentik.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
james merged commit 5afe834f69 into main 2026-06-18 12:08:47 +00:00
james deleted branch 102-redirect-honor-app-url 2026-06-18 12:08:47 +00:00
Sign in to join this conversation.
No description provided.