# Auth Proxy — Production Ingress Requirement The application uses **same-origin authentication**: the browser and React Router SSR both hit `/api/auth/*` on the public application domain. This keeps cookies first-party, avoids cross-origin credentials, and gives development and production the same API surface. ## Required production route At the public application domain, route: | Prefix | Target | | ------------- | --------------------- | | `/api/auth/*` | Convex HTTP site | | `/*` | React Router frontend | ### Caddy ```caddy zopu.example.com { handle /api/auth/* { reverse_proxy https://befitting-dalmatian-161.convex.site { header_up Host befitting-dalmatian-161.convex.site } } handle { reverse_proxy frontend:3000 } } ``` ### Dokploy / Traefik Create a higher-priority path router for `/api/auth` that forwards to the external Convex site URL. Ensure it: - Preserves the original browser `Cookie` header - Passes `Set-Cookie` responses back (rewrite domain if Convex emits an explicit one) - Preserves the original method and body - Forwards `X-Forwarded-Host` and `X-Forwarded-Proto` - Passes the full path unchanged (e.g. `/api/auth/convex/token`) - Does not cache auth responses - Allows OAuth callback routes under the same prefix ## Convex environment The Convex deployment `SITE_URL` must match the public origin users visit, not the Convex site URL: ```bash # Production npx convex env set SITE_URL 'https://zopu.example.com' # Local npx convex env set SITE_URL 'http://100.101.157.28:5173' # Staging npx convex env set SITE_URL 'https://zopu.cheaptricks.puter.wtf' ``` This value is Better Auth's public `baseURL` and `trustedOrigins`. It must be the browser origin because Better Auth writes OAuth state cookies and receives provider callbacks through the same-origin proxy. `CONVEX_SITE_URL` remains the internal Convex HTTP site URL. ## One origin per shared OAuth deployment GitHub OAuth Apps expose one authorization callback URL. The value must exactly equal `/api/auth/callback/github`, including scheme, host, port, and path. A GitHub authorization request whose `redirect_uri` differs returns `Invalid Redirect URI` before the user can grant access. `SITE_URL` is also single-valued in a Convex deployment. Therefore, two browser origins that share one deployment—such as Tailscale development and Puter staging—cannot use GitHub OAuth at the same time. Either switch both the Convex `SITE_URL` and the GitHub OAuth App callback together before testing the other origin, or give each public environment its own Convex deployment and OAuth App. Never set Better Auth's `baseURL` to `CONVEX_SITE_URL` for the browser flow. The browser creates the OAuth state cookie on `SITE_URL`; a provider callback sent directly to `CONVEX_SITE_URL` cannot read that cookie and fails with `state_mismatch`. ## Why same-origin 1. **First-party cookies.** No `SameSite=None`, no third-party cookie restrictions. 2. **SSR consistency.** The React Router server uses the same auth surface the browser sees; no cross-host credential translation. 3. **No CORS complexity.** The browser talks only to its own origin. 4. **The reverse proxy handles the cross-host hop server-side.** ## Related - [Better Auth: Trusted Origins](https://github.com/better-auth/better-auth/blob/main/docs/content/docs/reference/security.mdx) - `apps/web/vite.config.ts` — Vite dev proxy (`/api/auth` → Convex site) - `packages/auth/src/web/auth-client.ts` — `window.location.origin` - `apps/web/src/lib/auth.server.ts` — SSR token loader via request origin