ZevAuth
Sign up

Getting started

Troubleshooting

The failures people actually hit, what each one means, and the fix.

“Signed out every time the tab reopens”

The session is persistent by default: the refresh token lives in localStorage, namespaced per environment, and a fresh page load restores the session from it. If your users are asked to sign in again after closing a tab or pressing back, something is interrupting that restore. In order of likelihood:

1. The very first request is being refused. On load the SDK fetches /v1/environment before restoring the session. If that request fails, the restore never runs and the app renders signed out even though the session is sitting in storage. Open the network tab and look at the first environment request. A 403 with a message about your origin is the origin lock, not a session problem; see the next section.

2. Storage is unavailable. In a private window, or a browser configured to block site data, localStorage throws. The SDK falls back to memory, which works perfectly and lasts exactly one tab. If your audience includes embedded webviews or kiosk browsers, test there. You can supply your own adapter:

createZevAuth({
  publishableKey: 'pk_live_...',
  storage: { get: ..., set: ..., remove: ... },
});

3. You are on @zevauth/js 0.2.4 or older. Those versions had exactly this bug: the refresh on page load succeeded and then forgot who the session belonged to, leaving a valid session in storage next to a signed-out UI. Upgrade to 0.2.5 or later; nothing else is needed.

4. Something else clears storage. A logout button that calls localStorage.clear(), an “accept cookies” library that wipes site data, a service worker cache purge. The SDK’s key is zevauth.session.<environmentId>; if it vanishes between loads, search your codebase for who removes it.

“This publishable key is not allowed from this origin”

The origin lock, not CORS. Our API sends CORS headers for every origin; security rests on the key and, for live keys in browsers, on this lock. It admits three kinds of origin: your proved domain, its subdomains, and the exact origins of your registered callbacks. Everything else is refused.

Two distinct messages:

  • “awaiting DNS verification”: the origin matches your domain, but the domain’s proof is missing. That happens after changing the domain, which clears the proof on purpose. Go to Go live, prove the new domain, and the pause lifts on its own.
  • “not allowed from this origin”: the origin does not match at all. Either the domain in Configuration → General is not what the page runs on, or the URL should be added under Domains & callbacks.

Development keys are not origin-locked, which is why localhost always works and the first refusal appears in production.

“The verification code arrived but there is nowhere to type it”

Your environment sends codes, but a screen built for links is showing. Check GET /v1/environment → emailVerification.strategies. <SignUp /> renders the right screen from that automatically; a custom UI should branch on it too.

“Sign in with ZevID comes straight back with an error”

The person is returned to your callback with ?error= and a human-readable error_description; <SignIn /> shows it automatically. The codes and what to do about each are in the API reference. The common one is zevid_expired: more than 15 minutes passed across OTP, MFA and consent.

“DNS record added but the check says not found”

A check that ran before the record existed teaches resolvers “no such name” for up to 30 minutes. It clears on its own; press Check after adding the record, not before. Details in Development and production.

Reading an error like the API means it

Every error is { "error": { "code", "message" } }. The message is written for people and safe to show; the code is what to branch on. A 401 is the credential, a 403 is permission (origin lock included), a 404 on a documented path usually means the wrong host for that surface.

Updated at, Wednesday, October 21, 2026