# Email sign-in for your app

The optional email identity API creates an end-user account only after the person verifies their email. Each project has a separate directory. It does not create a Mocco console account or link accounts by email across projects.

Create a project key with `identity:auth`. Existing keys do not gain this scope automatically. Use the key in `X-Mocco-Key`; a browser origin must belong to the project's web origins. A backend-for-frontend should use its configured application origin, keep tokens in HttpOnly cookies, check the Origin of browser writes and mark private responses `no-store`.

All paths below are relative to `/v1/identity` on the configured public API host, or `/api/ext/v1/identity` on the application host.

| Method and path | Body or credential | Result |
| --- | --- | --- |
| `POST /email/start` | `{ "email": "reader@example.com" }` | `202 { challengeId }` |
| `POST /email/verify` | `{ challengeId, code }` | Access token, refresh token, `expiresIn`, and user |
| `POST /refresh` | `{ refreshToken }` | A new token pair; the old refresh token is consumed |
| `GET /me` | Access JWT in `Authorization: Bearer …` plus the project key | `{ id, projectId, email }` |
| `POST /logout` | `{ refreshToken }` | `204`, revoking that session family |
| `GET /config` | Project key and allowed origin | Project ID, issuer, audience, ES256 algorithm and access-token lifetime; no user data |
| `GET /jwks` | None | Public ES256 verification keys |

Before enabling sign-in, a backend can read `/config` with its project key and application Origin and compare the returned project ID and audience to its intended project. Query parameters cannot change that scope. This endpoint is `no-store`; it sends no email and creates no account, challenge or session. Reading configuration and JWKS does not verify email delivery.

A code expires after ten minutes and permits at most five guesses. Requests are rate-limited per email and client address. The start response does not disclose whether an account already exists. The database stores HMAC digests of codes and refresh tokens, not the original credentials. An SMTP delivery failure invalidates its challenge.

Access tokens last fifteen minutes. Their issuer is `<IDENTITY_ISSUER>/projects/<projectId>` and their audience is the project ID. Verify the issuer, audience and ES256 algorithm when using JWKS. `/me` also checks the live session, so logout takes effect there immediately. Offline JWT verification cannot observe revocation before token expiry.

A session lasts thirty days from sign-in; rotation does not extend that deadline. Reusing a consumed refresh token revokes the entire family. Serialize refresh, verification and logout in clients that share cookies, including across tabs. A backend should refuse to retry a pending write under a different signed-in subject.

Mocco feedback accepts these live sessions alongside the existing customer-signed HS256 identity tokens. It continues to scope verification by workspace and project. Messenger retains its separate contact-session and signed-identity flow; this slice does not automatically exchange identity tokens for messenger sessions.

## Self-host configuration

Apply migration `0109_email_identity` before enabling the feature. Configure all of:

- `EMAIL_DRIVER=smtp`, `EMAIL_FROM`, `EMAIL_SMTP_URL` — the existing SMTP sender.
- `IDENTITY_TOKEN_PEPPER` — a separate random secret of at least 32 characters.
- `IDENTITY_SIGNING_PRIVATE_KEY` — an ES256 PKCS#8 PEM; escaped newlines are accepted.
- `IDENTITY_ISSUER` — the stable HTTPS issuer base. HTTP is allowed only on loopback for local tests.

No operator secret is used as a fallback. The log email driver is refused, so sign-in codes never reach the development email log. Missing or invalid identity settings leave identity routes unmounted and keep other products available. Keep the pepper stable: changing it invalidates outstanding codes and refresh tokens.

This slice provides embedded email sign-in and session verification, plus a project’s **App users** console. Workspace owners and administrators can search, export one identity, revoke sessions, block or allow sign-in, and delete an identity account. Deletion removes the identity account and its sessions; other product content is retained under its existing identity ID. These actions are audited without storing tokens or email addresses in audit payloads.

The same service backs `mocco_identity_users_list`, `mocco_identity_users_get` and `mocco_identity_users_change`. Changes require the `identity:write` scope, workspace agent opt-in and a confirmed round trip. Role and project authorization are rechecked when the change applies.

Hosted redirects, social sign-in, passwords, passkeys and account linking are outside this slice. Operator sign-in remains separate.

## Verification

The backend suite covers project isolation, expiry, the five-attempt limit, concurrent code consumption, refresh replay and live revocation. The separate `identity-session.pg.test.ts` accepts `IDENTITY_TEST_DATABASE_URL` for an explicit scratch PostgreSQL database and exercises logout racing with a new refresh generation on independent connections. It applies the real migrations; never point it at an operating database.
