Troubleshooting¶
The errors you will actually hit, and what each one means.
verifySiteChallenge is undefined (the engine half-loaded)¶
You imported the engine files individually. They are classic scripts that
expect one shared scope, so importing them as modules gives each its own and
they stop seeing each other — and it fails partially, which is worse:
globalThis.secp256k1 survives because it is assigned, and everything that is
merely declared does not.
Import the bundle instead:
If it is missing or stale, rebuild it from a clone of the provider:
node server/build-engine.js. (@omxus/auth is not on npm yet.)
redirect_uri is not registered for this client¶
Exact string match, deliberately. Check for a trailing slash, http against
https, a port, and www. A dev port is a separate registration, not a
wildcard.
client_secret is required for this client¶
You registered a confidential client and are calling from a browser. A browser
cannot keep a secret; register a second, public client with
token_endpoint_auth_method: "none" (the default) for the browser code. PKCE is what stands in for the secret, and the provider
requires it either way.
TypeError: Failed to fetch from a browser client¶
CORS. Discovery, JWKS, token, userinfo and register send the headers
(Access-Control-Allow-Origin: *, no credentials); /oidc/authorize
and /auth/* deliberately do not, because those are cookie-bearing and are
meant to be reached by navigating, not by fetch.
If your own provider build predates 9 Sep 2026, it has no CORS headers at all and no browser client can reach it.
id_token audience mismatch / issuer mismatch¶
The token was minted for someone else, or by someone else. Both are refusals
working correctly. Check that clientId in your client matches the
registration, and that issuer matches the discovery document exactly —
including whether it has a trailing slash.
state does not match — discarding this callback¶
Either the sign-in was started in a different browser or tab, or someone tried
to plant an authorization code in your user's browser. Start again. If it
happens every time, your transaction store is not surviving the redirect —
sessionStorage is the default and it is per-tab.
no sign-in is in flight in this browser¶
handleCallback() ran without a matching signIn(). Usually a page that
calls it on every load, including after the transaction has been consumed.
Check for a code parameter before calling it.
The signature verifies locally and the server rejects it¶
The origin in the signed bytes is not the origin the server expects. In
development this is nearly always a proxy: the page is on one port and the
provider on another. Read the issuer from
/.well-known/openid-configuration, never from window.location.
The sign-in button sits on "Deriving…" forever¶
A biometric prompt that is never answered. navigator.credentials.create()
returns a promise that never settles in that case — no error, no cancel. The
SDK bounds it and falls back to the device key; if you call the underlying
functions directly, bound it yourself.
"That phrase has a typo in it"¶
The BIP-39 checksum. One word is wrong. This message exists because without the check, a mistyped phrase derives a different, empty account that looks exactly like a stolen one.
Note that validateMnemonic() is async and returns { valid, error }, not a
boolean — if (validateMnemonic(p)) and if (await validateMnemonic(p)) both
accept every string.
Someone lost their phrase¶
It depends on how they signed up.
- Recovery phrase or three inputs only. Nothing can be done. There is no reset, no support override, no recovery address. That is the trade for having no company able to lock anyone out; say it plainly on any screen that shows a phrase for the first time.
- Email and password. "Forgot password" mails a one-time link (15 minutes). It returns the recovery copy that was sealed to the provider's key at sign-up, and the person sets a new password on the same identity. It needs the provider's recovery key: see Email and texts.
- An ordinary account on the standard door. Sign in with a one-time code to their email or phone, then set a new password. See Your own sign-in page.
Checked against engine/mnemonic.js, server/router.js (CORS list),
server/register.js, server/email/email-routes.js and live CORS headers,
30 Oct 2026.