Auth & Tenant Resolution
Customer authentication and host-based tenant resolution in the storefront.
The storefront handles two cross-cutting concerns: customer authentication (Better Auth) and tenant resolution (which merchant store to display).
Customer authentication
Setup
Better Auth is configured in lib/auth/server.ts:
| Setting | Value |
|---|---|
| Provider | Email/password only |
| Database | Neon via Drizzle (lib/auth/db.ts, lib/auth/schema.ts) |
| Tables | user, session, account, verification |
| Organization plugin | Not enabled — buyers are not org members |
Auth seam
lib/auth/index.ts exposes getSession() and getCurrentUser() as thin Better Auth wrappers for pages and server actions.
Commerce identity
Logged-in buyers map to customers.id (internal UUID) via customers.auth_user_id. Orders and carts never store Better Auth user.id or email. See Privacy & data.
On sign-up, ensure a commerce customer row exists for the tenant (ensureCustomerForAuthUser).
Protected routes
Account pages check for an active session:
const session = await getSession()
if (!session) redirect('/login')The API resolves the session → customers.id and filters orders server-side.
API routes
Better Auth handler at /api/auth/[...all] handles sign-up, sign-in, sign-out, and session cookies.
Tenant resolution
How it works
lib/tenant.ts → resolveTenantId():
- Custom domain lookup (
tenant_domain, verified) - Subdomain
{slug}.{NEXT_PUBLIC_PLATFORM_DOMAIN}→ organization by slug DEFAULT_TENANT_ORG_ID(local dev only)- Otherwise
notFound()in production
There is no hardcoded org_demo fallback — set DEFAULT_TENANT_ORG_ID explicitly after pnpm db:setup seed.
Resolution priority
| Priority | Source | Example |
|---|---|---|
| 1 | Custom domain (verified) | shop.merchant.com |
| 2 | Platform subdomain | merchant.prood.app |
| 3 | DEFAULT_TENANT_ORG_ID | Local dev only |
| 4 | Not found | 404 |
Database lookup
lookupTenantByHost() from @prood/platform (re-exported in lib/tenant-db.ts).
Forwarding to API
The storefront API client forwards the Host header and session cookie to the Commerce API for tenant-scoped routes.
Development setup
- Run
pnpm db:setupand note the seeded organization id - Set
DEFAULT_TENANT_ORG_ID=<seeded-org-id>in.env.local - Access http://localhost:3000
For subdomain testing, add /etc/hosts entries and set NEXT_PUBLIC_PLATFORM_DOMAIN.