feat: same-origin auth via reverse proxy and secure cookies
Route /api/auth through the same origin in both dev and prod so cookies stay first-party and the browser and React Router SSR share one auth surface. - Vite dev server proxies /api/auth to the Convex HTTP site. - Better Auth uses secure cookies when the site URL is https. - Add docs/auth-proxy.md documenting the required production ingress (Caddy/Traefik) and Convex SITE_URL / CONVEX_SITE_URL setup.
This commit is contained in:
@@ -7,14 +7,17 @@ import { defineConfig } from "vite-plus";
|
||||
export default defineConfig({
|
||||
envDir: path.resolve(import.meta.dirname, "../.."),
|
||||
plugins: [tailwindcss(), reactRouter()],
|
||||
ssr: {
|
||||
noExternal: true,
|
||||
},
|
||||
resolve: {
|
||||
dedupe: ["convex", "react", "react-dom"],
|
||||
tsconfigPaths: true,
|
||||
},
|
||||
server: {
|
||||
allowedHosts: true,
|
||||
proxy: {
|
||||
"/api/auth": {
|
||||
changeOrigin: true,
|
||||
target: "https://befitting-dalmatian-161.convex.site",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
78
docs/auth-proxy.md
Normal file
78
docs/auth-proxy.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# 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 populates Better Auth's `trustedOrigins`. The Convex deployment also uses
|
||||
`CONVEX_SITE_URL` as the internal `baseURL` for route registration; that value is the HTTP
|
||||
site URL (e.g. `https://befitting-dalmatian-161.convex.site`).
|
||||
|
||||
## 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
|
||||
@@ -17,6 +17,7 @@ export const authComponent = createClient<DataModel>(components.betterAuth);
|
||||
|
||||
const createAuth = (ctx: GenericCtx<DataModel>) =>
|
||||
betterAuth({
|
||||
advanced: { useSecureCookies: siteUrl.startsWith("https://") },
|
||||
baseURL: env.CONVEX_SITE_URL,
|
||||
database: authComponent.adapter(ctx),
|
||||
emailAndPassword: {
|
||||
|
||||
Reference in New Issue
Block a user