Auth reference

The surface. Using Auth is the same ground with the code you write around it.

Config options

OptionDefaultMeaning
basePath/authWhere the handler mounts. Must match the OAuth redirect URIs you register
baseURLPUBLIC_ORIGINThis 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
disableSignUpfalseWhen 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, facebookoff{ 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

BindingTypeWritten byWhat it is
DBd1pithy addWhere the tables live
AUTH_RATE_LIMITERratelimitpithy addThe 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

SecretOriginRotation
auth-session-secretMinted. Any random string workslocal
auth-google-credentialsGoogle Cloud Consolemanual
auth-apple-credentialsThe Apple Developer portalmanual
auth-github-credentialsGitHub developer settingsmanual
auth-facebook-credentialsThe Meta app dashboardmanual

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.

MethodPathScope
GET/auth/admin/usersauth:users:read
GET/auth/admin/users/:userIdauth:users:read
GET/auth/admin/devicesauth:devices:read
POST/auth/admin/sessions/revokeauth:sessions:revoke
POST/auth/admin/users/:userId/sessions/revokeauth:users:logout
POST/auth/admin/users/:userId/devices/revokeauth: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

HeaderMeaning
x-pithy-device-idA client-generated stable id
x-pithy-platformios, android or web
x-pithy-device-nameA human label
x-pithy-push-tokenThe 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.

ESC