Running a provider¶
What it takes to put your own provider somewhere real, and what is missing before you should.
One is already running: https://auth.omxus.com, on the Workers and D1 free tiers. To add your site to it, use Registering a site; you do not need your own provider for that.
What a provider is made of¶
worker/index.js the Cloudflare Worker: reads env, builds the router
server/router.js every endpoint; Request in, Response out
server/oidc.js the protocol: authorize, token, userinfo, JWKS
server/auth.js challenge → verify → session
server/door.js which sign-in screen a client gets, and rendering it
server/store.js storage; in-memory and D1 implementations
server/db-apply.mjs the list of SQL files, in the order they run
engine/engine.bundle.mjs the crypto, as one importable module
frontend/dist/ the designed sign-in screen, once built
wrangler.toml the deployment: vars, D1, assets, email, cron, route
router.js takes plain Request objects and returns plain Response
objects. worker/index.js is the only host that is kept working; another
runtime (Deno, Bun, node) needs its own short adapter that passes the same
options. Untested: the old node demo under docs/oidc-demo/ no longer starts.
What you have to provide¶
Three secrets. Print them once per deployment:
It prints a wrangler secret put line and a value for each:
| Secret | What it is | If you change or lose it |
|---|---|---|
OIDC_SIGNING_JWK |
the provider's ES256 private key; signs ID tokens | every token in flight stops verifying (at most an hour) |
PAIRWISE_SALT |
server secret behind every pairwise sub |
every person's sub changes at every site; treat it as permanent |
RECOVERY_JWK |
P-256 key that opens sealed email-recovery secrets | lose it and email accounts lose "forgot password"; leak it and a stolen database becomes recoverable |
The signing key is the provider's own and has nothing to do with anyone's
identity key. Never reuse the recovery key as the signing key. The worker
refuses to start without OIDC_SIGNING_JWK.
A D1 database with every migration applied. server/db-apply.mjs lists
20 SQL files, applied in order; npm run worker:db:local applies them to the
local database. npm run worker:db:remote applies the same 20 plus
server/accounts/avatar-uploads.sql to the remote one. Both scripts name the
database omxus-auth-v2: create your own (wrangler d1 create <your-db>) and
change the name in package.json and wrangler.toml, with your
database_id.
server/oidc-clients/seed-clients.sql is one of those files. It inserts
Omxus's own clients, whose redirect addresses are on omxus.com; on your
provider they do nothing for anyone else.
Your own values in wrangler.toml. Under [vars], the ones that are
yours to set:
| Var | Set it to |
|---|---|
ISSUER |
your https address. It is baked into every token and every signature people make; changing it later invalidates both |
RP_ID |
the host name passkeys are bound to, usually the issuer's host |
FIRST_PARTY_CLIENTS |
your own sites' client ids (they skip the first-visit "Continue"), or empty |
TRUST_CORE, BOOTSTRAP_CITIZENS, CURRENT_QUARTER |
vouching (see Vouching); the values there are Omxus's own |
Also account_id, and the [[routes]] pattern and zone, for your domain.
Email and texts, for codes. Optional. Without them, email and phone sign-up say they are unavailable; everything else runs. See Email and texts.
Optional secrets. ACCOUNTS_OPERATOR_SECRET (and
ACCOUNTS_OPERATOR_IPS) turn on the operator routes.
SETTLE_SECRET turns on the vouch minting queue; unset, it does not exist.
Registering clients¶
On auth.omxus.com, sites register themselves: see
Registering a site. Your own provider has the same
/developers page and POST /oidc/register, so the same steps work against
your issuer.
To add one by hand, a row in oidc_clients is a client:
npx wrangler d1 execute <your-db> --remote --command \
"INSERT INTO oidc_clients (client_id, name, redirect_uris, secret_hash, sector_identifier, door)
VALUES ('my-site','My Site','[\"https://example.com/callback\"]',NULL,NULL,NULL)"
secret_hash is NULL for a browser client and sha256Hex(secret) (from
server/oidc.js) for a server-side one. redirect_uris is JSON, and each
entry is matched character for character. door picks the sign-in screen:
minimal (the dark card), full (the whole Omxus app) or standard
(ordinary accounts; also needs the standard door's own columns, so register
those through /oidc/register). NULL reads as minimal.
The designed sign-in screen¶
That writes frontend/dist/, which wrangler.toml serves as static assets
([assets], binding ASSETS). A client on the full door gets that page;
without a build, it falls back to the plain page in server/signin-page.js.
The minimal door is rendered by the server and needs no build.
Serve it from the provider's own origin. The page's calls to /auth/* need to
be same-origin for the session cookie, and it reads the issuer from the
provider's own discovery document rather than from the page URL. Assume the
page origin and every signature is rejected.
Deploying¶
From the auth repository, with wrangler.toml holding your values:
npm run worker:db:remote
wrangler secret put OIDC_SIGNING_JWK # and PAIRWISE_SALT, RECOVERY_JWK
cd frontend && npm run build && cd ..
npm run worker:deploy
worker:deploy builds the /sdk/ files first, then runs wrangler deploy.
The hourly cron in wrangler.toml deletes expired codes, challenges, tokens,
sessions and throttle rows, and audit rows older than 90 days.
Cloudflare Workers free tier, D1 free tier and static assets, on a domain you already own: nothing per month, with headroom for roughly 30,000 sign-ins a day. To try it on your machine first, see Run it locally.
Before you call it production¶
Read the security model first. Two things are
still missing: a scope-by-scope consent screen and back-channel logout.
Built and live: refresh tokens on the standard door, self-service client
registration (ten an hour from one address, see
Registering a site), and sign-in throttling (429
too_many_requests, wait 15 minutes).