> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pylonsync.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Auth overview

> Sessions, magic codes, password, OAuth, RBAC, API keys — what Pylon ships out of the box.

Pylon ships a complete auth system in the binary. You don't bolt on Auth0, Clerk, or NextAuth — sign-in flows, sessions, RBAC, and OAuth callbacks are all native HTTP endpoints. This page is the map; the rest of this section drills into each method.

## 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 — `pk.…` bearer tokens, optional scopes + expiry, server-issued at `/api/auth/api-keys`                                                                                                                                                                   |
| HIBP password check          | Argon2 + Pwned Passwords k-anonymity check at register/change. Disable with `PYLON_DISABLE_HIBP=1` (tests)                                                                                                                                                            |
| Password change/reset        | `/api/auth/password/change` (requires current password); forgot-password via `/api/auth/password/reset/request` + `/reset/complete` (emailed single-use link)                                                                                                         |
| Account deletion             | `DELETE /api/auth/account` — revokes sessions, API keys, OAuth links + deletes user row                                                                                                                                                                               |
| TOTP / 2FA                   | RFC 6238 6-digit codes. `/api/auth/totp/enroll` + `/verify` + `/disable`. Compatible with every authenticator app                                                                                                                                                     |
| WebAuthn / passkeys          | ES256 + Ed25519. Counter-regression detection. Endpoints: `/api/auth/passkey/{register,login}/{begin,finish}` + key management. Compose `verify_assertion` directly for custom flows.                                                                                 |
| JWT sessions                 | Optional stateless `Authorization: Bearer <jwt>` mode. Set `PYLON_JWT_SECRET` to enable; `/api/auth/jwt` mints from a session                                                                                                                                         |
| CAPTCHA                      | hCaptcha + Turnstile + reCAPTCHA. Set `PYLON_CAPTCHA_PROVIDER` + `PYLON_CAPTCHA_SECRET` to gate magic-code send + password register                                                                                                                                   |
| Organizations                | Multi-tenant orgs + RBAC (owner/admin/member) + email invites with single-use tokens. Endpoints under `/api/auth/orgs/...`                                                                                                                                            |
| Stripe billing               | Hosted Checkout sessions + signed webhook verification. Set `PYLON_STRIPE_API_KEY` + `PYLON_STRIPE_WEBHOOK_SECRET`                                                                                                                                                    |
| Phone / SMS sign-in          | E.164-normalized magic codes via Twilio (or any SmsSender). Endpoints under `/api/auth/phone/...`                                                                                                                                                                     |
| SIWE (Sign-In With Ethereum) | EIP-4361 + k256 ECDSA recovery + Keccak-256. Endpoints: `GET /api/auth/siwe/nonce`, `POST /api/auth/siwe/verify`.                                                                                                                                                     |
| OIDC Provider                | Full auth-code + PKCE flow: `/.well-known/openid-configuration`, `/oidc/jwks`, `/oidc/authorize`, `/oidc/token`, `/oidc/userinfo`. RS256 id\_tokens signed with an auto-generated, disk-persisted RSA-2048 key. Clients registered via `PYLON_OIDC_CLIENTS` JSON env. |
| SCIM 2.0                     | `/scim/v2/Users` POST/GET/PATCH/PUT/DELETE with `userName eq "..."` filter support + `/scim/v2/{ServiceProviderConfig, Schemas, ResourceTypes}` discovery — bearer auth via `PYLON_SCIM_TOKEN`. SCIM `active=false` → soft delete.                                    |
| 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 a `Session`, returns a token, the client stores it.** Every authenticated request then carries `Authorization: Bearer <token>`.

```
client → POST /api/auth/<method>     →  { token, user_id, expires_at }
client → request with Authorization  →  server resolves session → AuthContext
```

The `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)                         |
| `/password/change`                  | POST              | Change password (requires current; HIBP-checked; revokes other sessions)          |
| `/password/reset/request`           | POST              | Email a single-use password-reset link (always 200; enumeration-safe)             |
| `/password/reset/complete`          | POST              | Consume the reset token, set the new password, revoke all sessions                |
| `/api-keys`                         | POST              | Mint an API key (returns plaintext exactly once)                                  |
| `/api-keys`                         | GET               | List the current user's API keys (no plaintext)                                   |
| `/api-keys/:id`                     | DELETE            | Revoke a single API key by id                                                     |
| `/account`                          | DELETE            | Wipe user — sessions, API keys, OAuth links, user row                             |
| `/totp/enroll`                      | POST              | Mint a TOTP secret + provisioning URL (re-enroll requires current code)           |
| `/totp/verify`                      | POST              | Confirm a TOTP code; finalizes enrollment on first success                        |
| `/totp/disable`                     | POST              | Remove TOTP (requires a current code)                                             |
| `/jwt`                              | POST              | Exchange the current session for a JWT-shaped token (requires `PYLON_JWT_SECRET`) |
| `/orgs`                             | POST              | Create an org (caller becomes owner)                                              |
| `/orgs`                             | GET               | List orgs the caller belongs to                                                   |
| `/orgs/:id`                         | GET, DELETE       | Org details / delete (owner only)                                                 |
| `/orgs/:id/members`                 | GET               | List members                                                                      |
| `/orgs/:id/members/:user_id`        | PUT, DELETE       | Change role / remove (admin+)                                                     |
| `/orgs/:id/invites`                 | POST, GET         | Send invite by email / list pending                                               |
| `/orgs/:id/invites/:invite_id`      | DELETE            | Revoke pending invite                                                             |
| `/invites/:token/accept`            | POST              | Accept an invite (must be logged in with the invited email)                       |
| `/billing/checkout` (under `/api/`) | POST              | Mint a Stripe Checkout Session for the current user                               |
| `/billing/webhook` (under `/api/`)  | POST              | Stripe webhook target (verifies signature)                                        |
| `/phone/send-code`                  | POST              | Send a 6-digit code via SMS (Twilio, configurable transport)                      |
| `/phone/verify`                     | POST              | Verify the code; mints session, creates user if new                               |
| `/siwe/nonce`                       | GET               | Mint a SIWE nonce for `?address=0x…` (EIP-4361 step 1)                            |
| `/siwe/verify`                      | POST              | Verify EIP-191 signature; mints session keyed on wallet address                   |
| `/passkey/register/begin`           | POST              | Issue a registration challenge (auth required)                                    |
| `/passkey/register/finish`          | POST              | Persist a new credential after the authenticator signs                            |
| `/passkey/login/begin`              | POST              | Issue an assertion challenge (no auth needed)                                     |
| `/passkey/login/finish`             | POST              | Verify assertion → mint session                                                   |
| `/passkey/keys`                     | GET, DELETE       | List / revoke the user's passkeys                                                 |
| `/.well-known/openid-configuration` | GET               | OIDC discovery doc (root path, no `/api/` prefix)                                 |
| `/oidc/jwks`                        | GET               | JWKS for verifying pylon-issued OIDC tokens                                       |
| `/scim/v2/Users`                    | POST, GET, DELETE | SCIM 2.0 user provisioning (Bearer `PYLON_SCIM_TOKEN`)                            |

## 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 prefixed `pylon_`.
* **Cookies** — when configured (see [Sessions](/auth/sessions)), the same token can ride in an `HttpOnly` cookie for browser apps. Set automatically on successful sign-in.

Both resolve to the same `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:

```typescript theme={null}
type AuthContext = {
  userId: string | null;       // null = anonymous
  isAdmin: boolean;            // bypasses every policy
  isGuest: boolean;            // stable anonymous id (cart/preferences)
  roles: string[];             // ["admin", "owner", ...]
  tenantId: string | null;     // active organization
}
```

In a function:

```typescript theme={null}
import { mutation, v } from "@pylonsync/functions";

export default mutation({
  args: { title: v.string() },
  async handler(ctx, args) {
    if (!ctx.auth.userId) throw new Error("sign in required");
    if (!ctx.auth.hasRole("editor")) throw new Error("forbidden");
    // ...
  },
});
```

In a policy:

```jsonc theme={null}
{
  "match": "Todo",
  "read": "auth.userId != null && data.authorId == auth.userId",
  "write": "auth.userId == data.authorId || auth.hasRole('admin')"
}
```

## 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](/auth/sessions)).
* **`/api/auth/session` POST is admin-gated** in production — clients can't forge sessions.

## Configuration

Minimal env (most apps):

```bash theme={null}
PYLON_ADMIN_TOKEN=<64+ random hex>     # required for admin endpoints
PYLON_SESSION_DB=/var/lib/pylon/sessions.db  # persistent sessions
```

OAuth — 25 built-in providers + any OIDC IdP. Set two env vars per provider you want to enable:

```bash theme={null}
PYLON_OAUTH_GOOGLE_CLIENT_ID=...
PYLON_OAUTH_GOOGLE_CLIENT_SECRET=...
PYLON_OAUTH_GOOGLE_REDIRECT=https://yourapp.com/api/auth/callback/google

PYLON_OAUTH_GITHUB_CLIENT_ID=...
PYLON_OAUTH_GITHUB_CLIENT_SECRET=...
PYLON_OAUTH_GITHUB_REDIRECT=https://yourapp.com/api/auth/callback/github

# Apple, Microsoft, Discord, Slack, Spotify, Twitch, Twitter/X,
# LinkedIn, Facebook, GitLab, Reddit, Notion, Linear, Vercel, Zoom,
# Salesforce, Atlassian, Figma, Dropbox, TikTok, PayPal, Kick, Roblox
# — same shape: <PROVIDER>_CLIENT_ID + _CLIENT_SECRET + _REDIRECT.

# Apple needs ES256 signing material:
PYLON_OAUTH_APPLE_TEAM_ID=ABCDE12345
PYLON_OAUTH_APPLE_KEY_ID=KEYID12345
PYLON_OAUTH_APPLE_PRIVATE_KEY=/path/to/AuthKey.p8

# Generic OIDC (Auth0, Okta, Keycloak, Cognito, …):
PYLON_OAUTH_AUTH0_OIDC_ISSUER=https://acme.auth0.com
PYLON_OAUTH_AUTH0_CLIENT_ID=...
PYLON_OAUTH_AUTH0_CLIENT_SECRET=...
```

See [OAuth providers](/auth/oauth) for the full list and provider-specific notes.

Email (for magic codes — picks up from environment):

```bash theme={null}
PYLON_EMAIL_PROVIDER=stack0      # or sendgrid | resend | webhook
PYLON_EMAIL_API_KEY=sk_live_...
PYLON_EMAIL_FROM=noreply@yourdomain.com
```

Auth flows (magic codes, password reset, invitations) fall back to this
`PYLON_EMAIL_*` provider, so a single config covers both auth and your
app's own `ctx.email`. To give auth its **own** sending identity —
separate key, separate from-address, separate sending domain/reputation —
set the `PYLON_AUTH_EMAIL_*` family, which takes precedence for auth email
only:

```bash theme={null}
PYLON_AUTH_EMAIL_PROVIDER=stack0
PYLON_AUTH_EMAIL_API_KEY=sk_live_...
PYLON_AUTH_EMAIL_FROM=noreply@auth.yourdomain.com
```

When `PYLON_AUTH_EMAIL_*` is set, app code's `ctx.email` does **not** see
it — it reads only `PYLON_EMAIL_*`. That separation lets a host back auth
email with a shared, locked-down key (e.g. on a dedicated sending
subdomain) without exposing it to arbitrary app sends. (This is exactly
how Pylon Cloud configures auth email.)

Cookie auth (browser apps):

```bash theme={null}
PYLON_COOKIE_DOMAIN=.yourdomain.com
PYLON_COOKIE_SAME_SITE=lax       # lax | strict | none (none forces Secure)
PYLON_COOKIE_SECURE=true         # forces HTTPS-only cookies
```

## Where to go next

### Sign-in methods

* **[Magic codes](/auth/magic-codes)** — the simplest sign-in flow; what most apps should use first
* **[Password](/auth/password)** — email + password with Argon2 hashing
* **[OAuth](/auth/oauth)** — Google + GitHub + 23 more built-in providers
* **[Passkeys](/auth/passkeys)** — phishing-resistant FIDO2 / WebAuthn
* **[Phone / SMS](/auth/phone)** — E.164 sign-in via Twilio (or any custom SmsSender)
* **[SIWE](/auth/siwe)** — Sign-In With Ethereum, EIP-4361
* **[SSO](/auth/sso)** — per-org OIDC + SAML, members sign in through their IdP

### Sessions + tokens

* **[Sessions](/auth/sessions)** — token lifetime, refresh, revoke, persistent storage, cookies
* **[API keys](/auth/api-keys)** — long-lived keys for server-to-server calls
* **[JWT sessions](/auth/jwt)** — stateless `Bearer <jwt>` mode for microservices

### Account features

* **[RBAC](/auth/rbac)** — roles, the active tenant, multi-org apps
* **[Organizations](/auth/organizations)** — multi-tenant orgs, invites, member management
* **[TOTP / 2FA](/auth/totp)** — RFC 6238 + backup codes
* **[Trusted devices](/auth/trusted-devices)** — remember-this-browser cookie
* **[Email verification](/auth/email-verification)** — confirm a user owns their email
* **[Account deletion](/auth/account)** — GDPR-style hard delete

### Hardening + integrations

* **[CAPTCHA](/auth/captcha)** — gate sign-in endpoints with hCaptcha / Turnstile / reCAPTCHA
* **[Stripe billing](/auth/billing)** — hosted Checkout + signed webhooks
* **[SCIM 2.0](/auth/scim)** — Okta / Azure AD user provisioning
* **[OIDC provider](/auth/oidc-provider)** — Pylon as IdP for other systems
