ZKAuth
Get started

Get started

Your first verified login

Five steps, end to end: create a project, grab a test key, and run a register/login round-trip where the password never leaves the device.

1. Create a project

Sign in to the dashboard and create a project. Each project provisions its own client on the ZKAuth engine, isolated from every other project.

Create your account ->

2. Copy your API keys

Every project ships with two keys. Use the test key while you build and the live key in production. Both are scoped to that one project and can be rotated at any time.

During setup, add your primary callback URL and exact browser origins. Callback URLs include the path users return to after email/device links; browser origins are only scheme, host, and optional port.

  • zka_test_... for local development and CI.
  • zka_live_... for production traffic.
.env.localbash
# server-only runtime configZKAuth project key = zka_test_...ZKAuth API base URL = https://api.zkauth.dev

Keep keys server-side

Treat keys like secrets. Production applications should put keys behind trusted server code, your own narrow backend proxy, or ZKAuth hosted proxy mode. Never commit keys or ship live keys inside a public bundle.

3. Install the client

The published JavaScript client wraps the proof handshake. (Prefer no dependency? Every step below is also a plain HTTPS call.)

bash
npm install zkauth-client# or: pnpm add zkauth-client / yarn add zkauth-client
lib/zkauth.tsts
import { ZKAuthSDK } from 'zkauth-client'
export const zkauth = new ZKAuthSDK({  apiKey: loadServerOnlyProjectKey(),  baseUrl: loadServerOnlyApiBaseUrl(),})

Browser-hosted flows can initialize with project slug and public client ID instead of a project API key:

browser-auth.tsts
import { ZKAuthSDK } from 'zkauth-client'
export const zkauth = new ZKAuthSDK({  hostedProxy: {    projectSlug: 'your-project-slug',    clientId: 'your_public_client_id',  },})

4. Register a user

On registration the client derives a zero-knowledge proof from the password locally and sends only that proof. The engine stores a verifier, never the secret.

ts
const result = await zkauth.register({  email: 'ada@example.com',  password: 'SecurePassword123!',  deviceInfo: { deviceName: 'Chrome on Mac', deviceType: 'desktop' },})
console.log('created user', result.data.userId)

5. Log in and verify

Login repeats the handshake: the engine checks the proof, applies replay protection, and returns a session.

ts
const res = await zkauth.login({  email: 'ada@example.com',  password: 'SecurePassword123!',  deviceInfo: { deviceName: 'Chrome on Mac', deviceType: 'desktop' },})
// The server verified a proof, never the password.console.log('session for', res.data.user.email)const token = res.data.session.token

That's a real ZK login

No password, password hash, or reversible secret crossed the network. The server only ever saw a proof it could verify.

6. Handle email and device gates

Registration sends a verification email, and login must stay blocked until the address is verified.

If the same user logs in from a new device, the engine returns a device-approval response and sends a separate approval email instead of silently trusting the device.

  • Your app should show a clear "check your email" state after registration.
  • Device approval and denial links are separate single-use decisions. Once one link succeeds, the other can no longer change the request.
  • Your callback URL should handle zkauth_action values for email verification, device approval/denial, and password reset.
  • If no safe callback is configured, ZKAuth shows hosted fallback pages instead of redirecting to an unknown URL.
  • Browser origins should stay exact, with no paths or wildcards, so preview and production domains are auditable separately.

Hosted helper pages

If you would rather not build these screens yourself, hosted sign-up/sign-in and hosted forgot-password/reset pages run through the project-bound hosted proxy, so project API keys never reach the browser.

  • Hosted sign-in verifies the engine login and can return a single-use handoff code to an allowlisted callback. Your backend redeems that code with the project API key and creates the app session cookie, so bearer tokens stay out of callback URLs.
  • The account security helper keeps the engine bearer token in memory and covers profile, account stats, recent activity, sessions, devices, and recovery.
  • It also handles shown-once recovery-code export, TOTP setup/reset, MFA disable, and hosted-origin passkey helper controls.
  • Terminating other active sessions stays fresh-session guarded, so the engine must confirm a recent first-factor session before it runs.

Where to next