# {{capProjectName}} {{capAppName}} — auth surface

A drop-in set of auth pages other apps in the project link to, so the login UI
lives in ONE place. Every page is a real form POSTing to the sibling api's
`/auth/*` routes (from [`@voltro/plugin-auth`](https://voltro.cloud/docs/plugins/auth))
with CSRF. Bilingual (en/de).

```bash
voltro init my-app --api=api-auth --web=frontend-auth
```

## Pages

| URL | Route it drives | Purpose |
|---|---|---|
| `/login` | `POST /auth/sign-in` | email + password |
| `/signup` | `POST /auth/sign-up` | register + auto sign-in (with a strength meter) |
| `/forgot` | `POST /auth/password-reset` | request a reset link (neutral 202) |
| `/reset?token=` | `POST /auth/password-reset/confirm` | set a new password |
| `/magic` | `POST /auth/magic-link` | request a passwordless sign-in link |
| `/verify?token=` | `POST /auth/magic-link/callback` | consume the link / confirm email |
| `/logout` | `POST /auth/sign-out` | revoke the session, back to /login |

The emailed reset link points at `/reset?token=…`; the magic / verification link
points at `/verify?token=…`. Delivery needs `sendEmail` wired on the api (via
`@voltro/plugin-mail`) — without it those routes still mint tokens and return
`202`, they just don't send.

## Safety built in

- **Open-redirect guard** (`lib/redirect.ts`) — after sign-in we only honour a
  same-origin, absolute-path `?next=`; a cross-origin or protocol-relative value
  falls back to `AUTH_HOME`. Unit-tested.
- **CSRF** — `lib/auth.ts` fetches a token from `GET /auth/csrf` and echoes it on
  every state-changing POST.
- **No account enumeration** — `/forgot` and `/magic` always show the same
  neutral "check your email" message.

## Files

```
app.config.ts                 web app; apis.auth → @{{projectName}}/api
src/lib/
  auth.ts                     csrfToken() + postAuth() over /auth/*
  redirect.ts(.test)          safeNext() open-redirect guard (unit-tested)
src/components/
  AuthShell.tsx               centered card + brand + locale switcher
  PasswordStrength.tsx        advisory 0–4 meter
src/pages/
  page.tsx                    / → redirect to /login
  login/ · signup/ · forgot/ · reset/ · magic/ · verify/ · logout/
src/locales/{en,de}.ts        bilingual catalogs (parity-enforced)
```

## Opt-in: standalone auth subdomain (shared-cookie SSO)

The default is a **path-based** auth surface mounted inside your app (`/login`,
`/signup`, …) — cookies are same-origin and nothing extra is needed. If instead
you want a dedicated `auth.acme.dev` that signs a cookie for the whole
`.acme.dev` family (so `app.acme.dev` and `cms.acme.dev` share one session),
deploy this app on its own subdomain and set the api's cookie domain
(`authRoutesPlugin({ cookieDomain: '.acme.dev' })`). The pages are identical; only
the deploy topology + cookie domain change.

## Opt-in: OAuth / SSO providers

The base `@voltro/plugin-auth` ships password + magic-link + reset. Third-party
sign-in (Google, GitHub, WorkOS, Auth0, Kinde, Supabase, generic OIDC) is a
SEPARATE provider plugin (`@voltro/plugin-auth-*`) with its own callback routes —
add the plugin on the api, then render its buttons here. We don't ship dead
OAuth buttons wired to routes that don't exist.

## Out of scope (V2)

- 2FA / passkeys — `@voltro/plugin-auth` has the primitives (`/auth/mfa/*`,
  `/auth/passkey/*`); the pages follow.
- Bot defence (hCaptcha / Turnstile) — behind an env flag, render the widget only
  when configured.
