What’s included
| Feature | How |
|---|---|
| Magic-code sign-in | 6-digit code emailed to the user; verify via /api/auth/magic/verify |
| Email + password | Argon2-hashed; /api/auth/password/register + /login |
| OAuth | Google + GitHub built in; CSRF-protected via state tokens |
| Sessions | Opaque 256-bit tokens, 30-day default expiry, refresh + revoke |
| Multi-tenant | Active org per session; row-scoped policies via auth.tenantId |
| Roles (RBAC) | auth.hasRole('admin') in policies; multi-role aware |
| API keys | First-class via the api_keys plugin — see API keys |
| TOTP / 2FA | Via the totp plugin |
| JWT validation | Via the jwt plugin (for accepting tokens minted elsewhere) |
| Guest sessions | Stable anonymous IDs for cart/preference state pre-login |
| Email verification | /api/auth/email/send-verification + /api/auth/email/verify |
The shape of every auth flow
Every sign-in method ends with the same pair: the server mints aSession, returns a token, the client stores it. Every authenticated request then carries Authorization: Bearer <token>.
AuthContext is what flows into your policies, your TypeScript functions (as ctx.auth), and /api/auth/me.
Endpoints
All under/api/auth/:
| Endpoint | Method | Purpose |
|---|---|---|
/me | GET | Resolve the current session — returns AuthContext |
/magic/send | POST | Email a 6-digit code |
/magic/verify | POST | Trade code for session token |
/password/register | POST | Create user + sign in |
/password/login | POST | Sign in with email + password |
/email/send-verification | POST | Email a code to the current user |
/email/verify | POST | Verify the user’s email address |
/providers | GET | List configured OAuth providers |
/login/:provider | GET | Begin OAuth (returns redirect URL or 302) |
/callback/:provider | GET, POST | Complete OAuth |
/refresh | POST | Rotate the session token |
/sessions | GET | List all sessions for the current user |
/sessions | DELETE | Revoke all sessions for the current user |
/session | DELETE | Revoke the current session (sign out) |
/select-org | POST | Switch active tenant |
/guest | POST | Mint a guest session with a stable anonymous id |
/upgrade | POST | Convert a guest session to a real user (admin / dev only) |
Sessions vs cookies vs bearer tokens
Pylon supports both:- Bearer tokens — pass via
Authorization: Bearer <token>header. Used by every SDK, native clients, and curl. Tokens are 256-bit opaque strings prefixedpylon_. - Cookies — when configured (see Sessions), the same token can ride in an
HttpOnlycookie for browser apps. Set automatically on successful sign-in.
Session server-side. No JWT, no signing key to rotate, no refresh-token dance — opaque tokens you can revoke.
Auth context
What every authenticated request gets:Security guarantees
- Tokens are CSPRNG-generated with 256 bits of entropy, prefixed
pylon_. No JWT signing key to rotate. - Magic codes are 6-digit numeric, expire after 10 minutes, burn after 5 wrong attempts, and use constant-time comparison.
- Magic-code create is throttled to 1 per email per minute.
- Passwords are hashed with Argon2id (the OWASP-recommended default).
- OAuth state is CSRF-protected via single-use tokens (10-minute expiry).
- Failed password logins run a dummy hash so timing doesn’t leak whether the email exists.
- Sessions can be persisted to SQLite so they survive restart (see Sessions).
/api/auth/sessionPOST is admin-gated in production — clients can’t forge sessions.
Configuration
Minimal env (most apps):Where to go next
- Magic codes — the simplest sign-in flow; what most apps should use first
- Password — email + password with Argon2 hashing
- OAuth — Google + GitHub
- Sessions — token lifetime, refresh, revoke, persistent storage, cookies
- RBAC — roles, the active tenant, multi-org apps
- API keys — long-lived keys for server-to-server calls