Fetch runtimes
WIPWriting the auth routes on Bun, Deno or Cloudflare Workers over auth.flows, and the building blocks for a route or adapter of your own
Every route is a plain (req: Request) => Promise<Response>, so it serves any Fetch runtime. On Bun:
import { AuthError, csrfGuard } from '@gentleduck/auth/core'
import { callerContext, errorResponse, executeIntents, readBodyJson } from '@gentleduck/auth/server/generic'
async function signIn(req: Request, ip: string): Promise<Response> {
const body = await readBodyJson(req)
if (typeof body !== 'object' || body === null) throw new AuthError('AUTH_INVALID_PARAMETERS')
const providerId: unknown = Reflect.get(body, 'providerId')
if (typeof providerId !== 'string') throw new AuthError('AUTH_INVALID_PARAMETERS')
const { intents } = await auth.flows.signIn({
input: Reflect.get(body, 'input') ?? {},
providerId,
...callerContext({ ip, userAgent: req.headers.get('user-agent') }),
previousSid: auth.transport.extract(req) ?? undefined,
})
return executeIntents(intents)
}
Bun.serve({
async fetch(req, server) {
try {
await csrfGuard(auth, req)
const { pathname } = new URL(req.url)
if (pathname === '/auth/signin') return await signIn(req, server.requestIP(req)?.address ?? '')
return new Response('Not found', { status: 404 })
} catch (err) {
return errorResponse(err)
}
},
})
Deno's Deno.serve and a Worker's fetch take the same function. csrfGuard passes a safe method, so it can sit in front of every route but the OAuth callback. previousSid lets the flow revoke the session the browser held before, so signing in again never leaves two.
Building blocks
@gentleduck/auth/server/generic is what every adapter is built from:
| Export | Does |
|---|---|
executeIntents(intents) | A flow's intents as a Response: cookies, redirect, JSON body, no-store. Refuses an unsafe redirect. |
errorResponse(err) | A thrown error as a Response, the cause of a 5xx logged and withheld. errorToHttp is the same as { status, body }. |
jsonResponse(status, body) | A JSON Response with no-store. |
readBodyJson(req), readBodyText(req) | The body, null past 100 KiB or when it does not parse. |
callerContext({ ip, userAgent }) | The caller fingerprint a flow takes, trimmed to what the session row holds. |
requestSecurity(auth, { caller }) | The hijack and anomaly hooks the actor wrappers run. |
nodeHeadersToFetch(headers) | A Node header object as Headers. |
isSafeRedirectUrl(url) | A same-origin path or an http(s) URL; check a redirect before handing it to a script. |
serializeCookie | The cookie writer the rest use. |