Passkey-only OpenID Connect provider

Half the coin is yours.
The other half proves it.

Fractessa is a self-hosted OpenID Connect provider and OAuth 2.0 authorization server where people sign in with passkeys only. No passwords, in any flow. It runs entirely on Cloudflare: one Worker with D1, R2, Durable Objects, Queues and Images.

Inspired by Pocket ID

Fractessa's features are inspired by Pocket ID. Its API keeps the routes, methods, status codes, JSON fields and error codes of Pocket ID 2.19.1, so clients and scripts written for Pocket ID keep working.

Your deviceprivate key
Fractessapublic key

Tessera hospitalis

An old idea of identity, made of public keys.

Romans sealed hospitality with a token broken in two. Each host kept one half, and years later the halves fitting together proved who you were. A passkey works the same way: the private half never leaves your device, the public half lives on the server, and only the pair signs you in.

Fractessa is built around that pair. Recovery options exist only to get you back to a passkey, never to replace it.

Everything an identity provider needs, nothing that needs a password.

Passkeys only

Discoverable WebAuthn login with no username field. Choose platform or roaming authenticators, require user verification, and decide whether synced passkeys are allowed.

Recovery without passwords

One-time codes, sign-in links by email, and sign in from another device: the new device shows a code, and a device already signed in approves it with its passkey.

A complete OIDC provider

Authorization code with PKCE, rotating refresh tokens, client credentials, device flow, PAR, introspection, userinfo, end session and back-channel logout.

Users, groups and claims

Group-restricted apps, custom claims per user or group, claim mapping policies per client, open or invite-only signup, and optional or required email verification.

Your own APIs

Register resource servers with permissions (RFC 8707) and grant them per client, for users or for the client itself. Personal API keys with scopes, renewal and expiry warnings.

Integrations

Outbound SCIM provisioning, client ID metadata documents (CIMD), federated client credentials from other identity providers, and a full audit log with sign-in notifications.

Speaks the standards your apps already know.

Point any OpenID Connect client at the discovery document and it finds everything else. Access tokens are signed JWTs, and the signing key belongs to your instance.

https://auth.example.com/.well-known/openid-configuration
  • authorization_code + PKCE
  • refresh_token rotation
  • client_credentials
  • device_code · RFC 8628
  • PAR · RFC 9126
  • introspection · RFC 7662
  • JWT access tokens · RFC 9068
  • resource indicators · RFC 8707
  • back-channel logout 1.0
  • RP-initiated logout
  • client ID metadata documents
  • JWT bearer client assertions
  • query · fragment · form_post
  • discovery + JWKS

Default scopes: openid profile email groups offline_access, plus the scopes of every API you register.

Architecture

One Worker. No servers to patch, no database to babysit.

Every part of the original single binary maps to a Cloudflare product. One-time tokens are consumed with a single atomic statement, and per-key state lives in Durable Objects.

Workers

API, OIDC endpoints, the admin app, cron jobs and queue consumers in a single TypeScript Worker.

D1

Users, clients, sessions, consents and tokens, with migrations applied before every deploy.

Durable Objects

Cross-device login with long polling, exact rate limiting, and debounced SCIM syncs.

Queues

Durable back-channel logout delivery, with retries and a dead letter queue.

R2 + Images

Logos and profile pictures, resized to square PNGs or generated from initials.

Cron Triggers

Hourly SCIM syncs and signup cleanup; nightly token, session and audit log cleanup.

Get started

You need Node.js 22 or newer, a Cloudflare account on Workers Paid, and a domain of your own on Cloudflare: passkeys are bound to the domain, and some password managers refuse *.workers.dev. SMTP is optional.

1Run it locally

# install dependencies
make install

# local D1, migrations, http://localhost:1411
make dev

With no users yet, the first visit creates the administrator and their passkey.

2Deploy to Cloudflare

npx wrangler login

# D1, R2, queues and secrets
make cf-setup

# checks, tests, build, migrations, deploy
make deploy

Set APP_URL and your custom domain in wrangler.jsonc first. Changing the URL later invalidates every passkey.

Read the full guide in the README