Auth / foldkit-auth
On Workers with D1
examples/cloudflare runs it end to end on
Miniflare: Yielded's routes (/auth/…) and /me, which answers through
AuthServer.resolver, in the same Worker as serveFetch
(src/authRoutes.ts); the account table and Yielded's own as a D1 migration
(migrations/0005_auth.sql); sign-up provisioning the account row; and a
test that signs up, signs in and reads the session back (test/auth.test.ts).
Three things it shows that the sections above do not:
- Persistence on D1 is
@yielded/auth-persistence-drizzle/D1. TheAuthPersistenceof@yielded/auth-persistencecommits a sign-in or a sign-up in an interactive transaction, which@effect/sql-d1does not run ("transactions are not supported in D1"). The Drizzle package's D1 module commits through D1's atomic batches instead; give itD1Client.layer({ db: env.DB })and its tables throughPersistence.managed({ subjects, prefix }). ItsPersistencehas theProvisioningandConfigof Signing up. - The migration is the application's. Yielded checks the columns and
unique keys it reads when it first runs, and creates nothing. Write each
managed table as Drizzle declares it; the example's test derives the
statements from
storage.schemaand compares. @yielded/auth-platform-workerdneedsnodejs_compatand a Wasm module. Itslayergives Yielded its crypto and password hashing on Workers; the hashing importsnode:cryptoand an Argon2.wasmfile, and a bundle that is not Wrangler's keeps the.wasmbeside the script as a compiled module. Node cannot load it, so keep the auth definition (tables included) in a module that does not import it, for tests to read.
Beyond those, a few settings a server on one machine never needs:
- Build auth once per isolate. Make
AppAuth's layer and its persistence over the D1 binding once, in aManagedRuntime, and provide its context to each request. A D1 binding is not I/O a request owns, so sharing it is safe. Built per request, Yielded runs its storage's first-use setup again with each build, and two requests carrying one cookie at once (a refresh beside a page's read) can fail session verification, whichserveFetchanswers as a 500. - Allow the clocks to differ. Yielded's
AuthenticationClockis strict by default (futureToleranceMillis: 0), and a session's issue time comes from the Worker while D1 keeps its own clock. A Worker'sDate.now()moves only between I/O, so a session can read as a moment in the future, and a correct password is refused asPasswordRejected. Give the clock some room:tsLayer.provideMerge(Operations.AuthenticationClock.layer({ futureToleranceMillis: 1000 })) - Expect the sign-in limit to be per isolate. Yielded counts password
attempts, successful ones included, per email and per account: by default
10, then one more every 6 seconds. It counts in memory unless the layer is
given an Effect
RateLimiterStore(effect/persistence/RateLimiter), so each isolate keeps its own count, which is lost when the isolate is evicted. Give the layer a shared store for a limit that holds across isolates. A test that signs one account in more than ten times in quick succession is refused asPasswordRejected, so sign each test account in once.