Authentication methods
Status: Accepted (2026-09-14) · Supersedes: ADR-0006 · Amended by: ADR-0038 (flows, tables and SQL live in the generated app's internal/modules/auth; modules/auth provides building blocks), ADR-0043 (2FA details; TOTP implemented in modules/auth instead of pquerna/otp), ADR-0044 (passkeys: sign-in and second factor, configuration in env, native association files), ADR-0046 (Google and Apple: API-hosted web flow, native ID tokens with server nonces, automatic linking that removes an unverified account's password, second factor still required)
Context#
The Full preset promises complete authentication on first run. Authentication is the highest-risk area: it must be secure by default, support web and native mobile clients, and receive security fixes through the library rather than copied code.
Options#
- JWT access + refresh tokens, social login through a vendor SDK (for example Firebase).
- External identity service (Ory, Keycloak, Auth0) required for every app.
- Embedded library with server-side sessions and standards-based social login, 2FA and passkeys; owned HTTP handlers in the app.
Decision#
Option 3. Logic lives in apistock.dev/modules/auth; the generated internal/modules/auth holds endpoints, customisable rules and adapters.
Methods and flows (Full preset)#
| Capability | Design | Milestone |
|---|---|---|
| Email + password | argon2id with stored parameters, rehash on login; minimum length 12; optional breached-password check hook | v0.2 |
| Email verification | 6-digit code, hashed, 15-minute expiry, attempt-limited, resend throttled | v0.2 |
| Sessions | Opaque 32-byte token; only SHA-256 stored; __Host- cookie (Secure, HttpOnly, SameSite=Lax) for browsers or Authorization: Bearer for native clients; idle and absolute expiry; rotation on login and privilege change |
v0.2 |
| Logout / logout all | Revoke one or all sessions | v0.2 |
| Password reset | Single-use hashed code or token, 30-minute expiry, identical response whether or not the email exists, all sessions revoked on success | v0.2 |
| Change password | Requires the current password; other sessions revoked | v0.2 |
| Active sessions | List devices; revoke one | v0.2 |
| Delete account | Soft delete, sessions revoked, purge job after retention period | v0.2 |
| Platform roles and permission catalog | Modules declare permissions; roles are sets of permissions; deny by default | v0.2 |
| Google sign-in | OIDC authorization code + PKCE with state and nonce (web); ID-token verification (native) | v0.3 |
| Apple sign-in | Same two flows; client secret signed per request from the .p8 key; name stored on first sign-in; private relay emails supported |
v0.3 |
| Account linking | Only on a verified email match | v0.3 |
| TOTP 2FA | Secret encrypted at rest with a rotatable app key; 10 hashed recovery codes; codes rate-limited and single-use; mfa_required challenge before a session is created |
v0.3 |
| Passkeys | WebAuthn registration and login; relying-party ID and allowed origins configured; counts as strong 2FA; apple-app-site-association and assetlinks.json served |
v0.3 |
| 2FA policy | Roles can require 2FA; ops roles require it | v0.3 |
| GitHub login, API keys | Custom option / v1.1 | v1.1 |
| SAML, SCIM, enterprise SSO | Delegated to identity providers | Later |
Social providers are included but disabled until credentials are configured; aps dev reports their status.
Security rules#
- No account enumeration on register, login or reset responses.
- CSRF protection for cookie-authenticated routes (
http.CrossOriginProtection). - Per-account and per-IP rate limits; no hard lockout.
- Security events recorded through
audit.Recorder(login succeeded/failed, MFA enrolled, passkey added, password changed, session revoked). - A new sign-in alert email for unrecognised devices.
Libraries#
golang.org/x/crypto/argon2, golang.org/x/oauth2, github.com/coreos/go-oidc, github.com/pquerna/otp, github.com/go-webauthn/webauthn.
Why#
- Revocable sessions are simpler and safer than JWT session tokens.
- Standards-based providers avoid vendor SDK lock-in.
- Security fixes ship through
go get.
Trade-offs#
- A large security surface to maintain; an external security review is required before 1.0.
- Apple web sign-in can't use
localhostredirects; local testing uses the native flow or a tunnel (documented).
Consequences#
- The data model for MFA challenges and role scopes is designed in v0.2 even though the features ship in v0.3–v0.4.
docs/auth-providers.mdin generated apps explains Google, Apple and passkey setup.