ZevAuth
Sign up

Getting started

Keys and environments

Which key goes where, and which one must never reach a browser.

Every environment has its own keys. They are not interchangeable, and the difference matters.

Publishable keys

pk_test_… and pk_live_…

These identify your environment. They are not secret. They ship inside your JavaScript bundle and anyone can read them. That is fine, because a publishable key on its own can do almost nothing: it can start a sign-in, and that is the point.

What stops somebody using yours on a phishing site is origin locking. A publishable key is accepted only from your project’s verified domain, its subdomains, or a callback URL you registered. A copy of your key on your-app.attacker.example is refused.

// Correct. This is meant to be public.
<ZevAuthProvider publishableKey="pk_live_..." />

Secret keys

sk_test_… and sk_live_…

These act as your whole account. They read any user, create organizations and change anything. They belong on a server, in an environment variable, and nowhere else.

The browser-facing endpoints, sign-in among them, accept either kind of key, because the same endpoint serves your frontend and your server. That means a secret key sent from a browser works, and nothing visibly breaks. It is still the one mistake that exposes the whole environment, so treat “it worked” as no evidence at all here.

Reading your publishable key

A publishable key is not a secret, so you can fetch it with your secret key rather than copying it out of the dashboard:

curl https://api.zevauth.net/v1/environment \
  -H "Authorization: Bearer sk_test_..." | jq -r .publishableKey

That is enough for a provisioning script, a CI job, or a preview environment per branch to fill in VITE_ZEVAUTH_PUBLISHABLE_KEY on its own.

Secret keys are the opposite: shown once when created, stored as a hash, and never readable again. If you lose one, rotate it.

Domain, callbacks, and CORS

Three different values guard three different things, and knowing which does what saves an afternoon:

What it isWhat it guards
CORSNothing you configureNothing. Our API sends CORS headers for every origin, on purpose: it is called from thousands of apps we cannot enumerate. Security never rests on it.
DomainOne value, in Configuration → GeneralWhich browser origins your live publishable key answers for: the domain and its subdomains, once the DNS proof exists. Also what ZevID verifies for your badge.
CallbacksA list, in Configuration → Domains & callbacksWhere a brokered sign-in may land, matched exactly. Each callback’s origin is also individually allowed through the origin lock, which is how a staging host works without being a subdomain.

So when a browser call from production fails, the answer is never “CORS”: it is the origin lock. Either the origin is not on the proved domain, or the domain’s DNS proof is missing (it is cleared whenever the domain changes), or the origin is not one of your registered callbacks. Development keys are not origin-locked at all, which is why everything works locally.

More than one callback is normal: your app, your staging host, a preview deployment. Register each exactly; there are no wildcards, because a prefix or host rule is how an open redirect hands someone a session.

Session tokens

Not a key. A session token is the short-lived JWT that represents one signed-in person, and it is what your own API should accept.

The three are used in different places:

CredentialLivesAnswers
Publishable keyYour frontend bundleWhich app is this?
Secret keyYour server, in an env varIs this the developer?
Session tokenMemory, in one browserWhich person is this?

Endpoints under /v1/me take a session token and nothing else. Handing them a publishable key is refused, with an error that says so. Otherwise any holder of a public key could read anybody’s profile.

Rotating a key

You can rotate keys in the console. A rotated publishable key stops working immediately, so deploy the new one first.

Rotating a secret key without downtime

An environment can hold more than one active secret key, so you never have to swap one for another in a single step:

  1. Create a second secret key in the console.
  2. Deploy it everywhere the old one is used.
  3. Watch Last used on the old key in the console. Once it stops moving, revoke the old key.

If a secret key may have leaked, revoke it straight away instead and accept the brief outage: a leaked secret key is full access to the environment. If you are a delegated-access partner, also tell ZevAuth support, so we can suspend your app on every product while you rotate.

Updated at, Wednesday, October 21, 2026