Simple Auth is a passkey-based authentication service for Cloudflare Worker. Use @znut/simple-auth-lib to verify session tokens exchanged from Simple Auth service.
After a successful login or registration, Simple Auth now:
- Signs a session token.
- Sets its own session cookie for the auth app:
__Host-simple_auth_sessionon HTTPS, orsimple_auth_sessionon HTTP localhost development. - Stores the session token behind a short-lived, single-use exchange code.
- Appends only that exchange code to the
nextURL assimple_auth_code.
If RETURN_URL_ALLOWLIST is not set, we only allows:
- Relative URLs on the auth app itself.
- Absolute URLs on the same origin as the auth app.
- Local development URLs is
localhostor end with.localhost
Example wrangler.jsonc config:
Example redirect:
https://app.example.com/auth/callback?simple_auth_code=9Gm...
The consumer app exchanges the one-time code with the auth service from its server-side callback route. The auth service signs session tokens with SESSION_PRIVATE_KEY_JWK; consumers verify with SESSION_PUBLIC_KEY_JWK
Install the library:
bun add @znut/simple-auth-libCreate a callback route that reads the token from the return URL, verifies it, and stores it in the app's own cookie jar.
On HTTPS, setSessionCookie writes the __Host-simple_auth_session cookie. On HTTP localhost development, it writes simple_auth_session so browsers will send the cookie on the next local request.
UNSAFE_DEV_MODE defaults to off; set it to "true" only for local HTTP development.
Example SvelteKit route:
// src/routes/auth/callback/+server.ts
import {
readSessionExchangeCode,
readSessionToken,
removeSessionExchangeCodeFromUrl,
resolveSessionCookieOptions,
setSessionCookie,
verifySessionToken,
} from "@znut/simple-auth-lib"
import { redirect } from "@sveltejs/kit"
import type { RequestHandler } from "./$types"
const authOrigin = "https://auth.example.com"
const unsafeDevMode = process.env.UNSAFE_DEV_MODE === "true"
const sessionPublicKey = process.env.SESSION_PUBLIC_KEY_JWK!
export const GET: RequestHandler = async ({ cookies, fetch, request, url }) => {
const code = readSessionExchangeCode(request)
if (!code) {
throw redirect(303, "/login?error=missing-code")
}
const returnUrl = removeSessionExchangeCodeFromUrl(url)
const exchangeResponse = await fetch(
`${authOrigin}/api/auth/session/exchange`,
{
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
code,
returnUrl,
}),
}
)
if (!exchangeResponse.ok) {
throw redirect(303, "/login?error=invalid-code")
}
const { token } = (await exchangeResponse.json()) as { token: string }
const session = await verifySessionToken(token, sessionPublicKey, {
audience: url.origin,
issuer: authOrigin,
})
if (!session) {
throw redirect(303, "/login?error=invalid-token")
}
setSessionCookie(
cookies,
token,
session.exp,
resolveSessionCookieOptions(unsafeDevMode)
)
throw redirect(303, "/")
}import {
readSessionToken,
resolveSessionCookieOptions,
verifySessionToken,
} from "@znut/simple-auth-lib"
const cookieOptions = resolveSessionCookieOptions(
process.env.UNSAFE_DEV_MODE === "true"
)
const token = readSessionToken(
event.cookies,
process.env.UNSAFE_DEV_MODE === "true"
)
const session = token
? await verifySessionToken(token, process.env.SESSION_PUBLIC_KEY_JWK!, {
audience: event.url.origin,
issuer: "https://auth.example.com",
})
: nullRun the deployment initializer before your first production deploy:
bun run deploy:initIt updates:
wrangler.jsoncproduction worker name and D1 name/idpackage.jsonmigration scripts with the production D1 database namesrc/lib/server/config.tsapp name shown in the website
Deployment checklist:
- Generate an ES256 P-256 signing key pair as JWK with
bun run key:generate. - Set
SESSION_PRIVATE_KEY_JWKonly on the auth service in Cloudflare WorkerSettings -> Variables and Secrets -> Secrets. - Set
SESSION_PUBLIC_KEY_JWKon the auth service and every consumer app that verifies Simple Auth tokens. - Set
RETURN_URL_ALLOWLISTfor cross-origin returns - Configure the deploy build command as
bun run build - Configure the deploy command as
npx wrangler deploy --env production - Set
CLOUDFLARE_ACCOUNT_IDandCLOUDFLARE_API_TOKENin GitHub if you want migrations from GitHub Actions - If those GitHub secrets are not configured, run
bun run db:migrate:remotelocally with Wrangler CLI instead
{ "vars": { "RETURN_URL_ALLOWLIST": "http://dashboard.ex.localhost:4173/auth/callback", "UNSAFE_DEV_MODE": "true", }, "env": { "production": { "vars": { "RETURN_URL_ALLOWLIST": "https://app.example.com/auth/callback,https://dashboard.example.com/auth/callback", }, }, }, }