Skip to content

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:

import '/path/to/engine.bundle.mjs';   // sets the functions on globalThis

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.