Polis civic circle logoPolis

Authentication

A Polis session is private, passwordless, and proof-bound.

The native email path exchanges a short-lived signed link for a `polis_session` HttpOnly cookie only after verified proof-of-personhood state is bound to the account.

Trust illustration for Polis authentication.

Magic-link email

`/api/auth/email/start` issues a short-lived login link through the configured no-cost outbound provider. Local CI uses `local_synthetic_sink` and never sends mail.

Proof binding

The verified proof binding lives in `polis_verified_pop_bindings`, keyed by email hash. A raw onboarding intake stays pending and never mints a production session; missing or revoked proof fails closed with `AUTH_POP_REQUIRED`.

HttpOnly refresh

`/api/auth/session/refresh` accepts only an existing signed session and returns a refreshed `polis_session` cookie with `HttpOnly; Secure; SameSite=Lax`.

Session mismatch denial

The Worker compares the cookie proof mode to the stored account proof mode and rejects mismatches with `AUTH_POP_SESSION_MISMATCH`.

Secret locations

Secret values live only in Infisical at `/polis/socialmedia2_com`: `POLIS_AUTH_SESSION_SECRET`, `POLIS_EMAIL_LOGIN_SIGNING_SECRET`, and provider keys by name.

Separate provider setup

Google OAuth and real outbound email provider setup are tracked separately in GitHub issues #132 through #149 and must remain no-cost unless explicitly approved.