The surface. Using Auth is the same ground with the code you write around it.
Config options
| Option | Default | Meaning |
|---|---|---|
basePath | /auth | Where the handler mounts. Must match the OAuth redirect URIs you register |
baseURL | PUBLIC_ORIGIN | This Worker’s public origin. Callbacks, the key set and magic-link URLs are built from it. A dev composition ignores it and serves on whatever host the request arrived at |
disableSignUp | false | When true, sign-in never provisions a new user. An unknown address gets no email at all — replying no such account is an enumeration oracle |
trustedOrigins | — | Every web origin and mobile deep-link scheme allowed as a redirect target and CSRF origin. Prefix match |
google, apple, github, facebook | off | { enabled: true } per provider |
plugins | [] | Additional Better Auth plugins, composed after the fixed four |
Dev needs neither baseURL nor trustedOrigins: it resolves its own address and trusts its own origin, because local dev has no TLS and its port is assigned per run.
Bindings
| Binding | Type | Written by | What it is |
|---|---|---|---|
DB | d1 | pithy add | Where the tables live |
AUTH_RATE_LIMITER | ratelimit | pithy add | The edge guard on every auth route |
The rate limiter is written at 100 requests per 60 seconds, per client IP — a flood guard rather than a product rule. Cloudflare accepts a period of 10 or 60 and nothing else.
Secrets
| Secret | Origin | Rotation |
|---|---|---|
auth-session-secret | Minted. Any random string works | local |
auth-google-credentials | Google Cloud Console | manual |
auth-apple-credentials | The Apple Developer portal | manual |
auth-github-credentials | GitHub developer settings | manual |
auth-facebook-credentials | The Meta app dashboard | manual |
Each provider credential is a typed JSON secret holding both halves together. Apple’s carries a third, optional field for the native flow’s audience, and its client secret expires within six months by construction.
The session secret is written only when absent — a new value signs out every live session.
Tables
All pithy_auth_*, all run by migrate:
users · sessions · accounts · verifications · jwks · rate_limit · devices
The first six are Better Auth’s. devices is Pithy’s own. A composed plugin adds its own under its own names, unprefixed.
Verification strategies
This is the only capability that implements bearer and session. Every other capability gates with the shared middleware and reads identity off the seam.
It also contributes a control-plane surface, which is a different thing entirely.
The management routes
Every one is control-plane and default-denied. They are shaped like every other route in the kit.
| Method | Path | Scope |
|---|---|---|
| GET | /auth/admin/users | auth:users:read |
| GET | /auth/admin/users/:userId | auth:users:read |
| GET | /auth/admin/devices | auth:devices:read |
| POST | /auth/admin/sessions/revoke | auth:sessions:revoke |
| POST | /auth/admin/users/:userId/sessions/revoke | auth:users:logout |
| POST | /auth/admin/users/:userId/devices/revoke | auth:devices:revoke |
Five scopes, not one admin flag. Reading a user is a privacy operation; revoking their sessions is an availability one. A support tool that looks people up should never be able to sign the customer base out, and an incident-response tool that kills a stolen session has no business reading every address in the user table.
Scope matching is exact — no prefix rule — so a broader-sounding string grants none of them.
Every call is audited, reads included, under the control-plane actor kind.
Rate limiting
Two limiters, two jobs.
Tier 1 is Cloudflare’s native limiter, per client IP, at the edge, with no storage round-trip. It runs in front of every auth route and blunts floods and credential-stuffing before they reach anything else.
Tier 2 is Better Auth’s own, database-backed, keyed per action and identity — the send-rate caps on magic link and one-time code. Database-backed because in-memory limiting is per-isolate on Workers and therefore useless.
Device headers
| Header | Meaning |
|---|---|
x-pithy-device-id | A client-generated stable id |
x-pithy-platform | ios, android or web |
x-pithy-device-name | A human label |
x-pithy-push-token | The push token |
Peer capabilities
Required: secrets, email.
Optional: turnstile — auto-gates the magic-link and one-time-code send routes with zero config. audit — records sign-in, token refresh and device revocation.
What is not here
Refusals carry an auth/ error code rather than a sentence a client has to parse.
No password. Ever.
No impersonation. No route mints a session and no read projects a session token.
No roles or permissions. Auth answers who is this.