ZKAuth
Get started

Reference

SDK reference

zkauth-client exports ZKAuthSDK, the official JavaScript/TypeScript client. It runs the password crypto locally (Argon2id, Poseidon, Groth16 via snarkjs), so the password never leaves the device, and keeps your session in memory after login.

Client and framework helpers

zkauth-client remains the crypto-capable JavaScript client for registration and login.

Server helpers: @zkauth/node, @zkauth/nextjs, @zkauth/express, and @zkauth/hono.
UI helper: @zkauth/react for app-owned React flows.
Use direct API mode from trusted server code, or hosted proxy mode for browser-hosted flows that should not receive a project API key.

Constructor

Pick one trust boundary: direct API mode with a server-side project key, or hosted proxy mode with a project slug and public client ID. Both modes keep password-derived proof generation on the client.

ts
import { ZKAuthSDK } from 'zkauth-client'
const zkauth = new ZKAuthSDK({  apiKey: loadServerOnlyProjectKey(), // direct API / server-side mode  baseUrl: 'https://api.zkauth.dev', // optional  timeout: 30000, // optional, ms  debug: false,   // optional})
browser-hosted modets
import { ZKAuthSDK } from 'zkauth-client'
const zkauth = new ZKAuthSDK({  hostedProxy: {    projectSlug: 'your-project-slug',    clientId: 'your_public_client_id',  },})
server callback handoffts
import { redeemZKAuthHostedHandoff } from '@zkauth/node'
export async function GET(request: Request) {  const url = new URL(request.url)  const code = url.searchParams.get('code')  const handoff = await redeemZKAuthHostedHandoff({    apiKey: loadServerOnlyProjectKey(),    baseUrl: loadServerOnlyApiBaseUrl(),  }, code)
  // Store handoff.sessionToken in your own HttpOnly, Secure, host-only cookie.  // Do not expose it in the URL or client-side storage.  return Response.redirect(new URL('/dashboard', request.url))}
server machine introspectionts
import { introspectZKAuthMachineToken } from '@zkauth/node'
const machine = await introspectZKAuthMachineToken(  {    apiKey: loadServerOnlyProjectKey(),    baseUrl: loadServerOnlyApiBaseUrl(),  },  {    audience: 'zkauth:client:00000000-0000-4000-8000-000000000001',    issuer: 'https://api.zkauth.dev',  },)
if (!machine.active) {  throw new Error('ZKAuth machine token is not active')}
OptionTypeNotes
apiKeystringRequired for direct API mode. Keep it server-side and omit it when hostedProxy is configured.
baseUrlstringEngine URL for direct API mode. Defaults to the hosted engine.
timeoutnumberRequest timeout in ms (default 30000).
debugbooleanVerbose logging.
clientIdstringOptional; fetched automatically if omitted.
hostedProxy{ projectSlug, clientId, baseUrl?, proxyPath? }Browser-safe mode that routes engine calls through ZKAuth hosted proxy routes. The proxy injects the project key server-side.

Hosted proxy boundary

Hosted proxy mode is an SDK transport option, not full hosted UI parity. ZKAuth-hosted sign-up/sign-in helper pages can register accounts and verify engine login, including MFA, MFA enrollment-required, or device-approval-required states.

For project-required MFA on an unenrolled account, hosted sign-in can use the short-lived enrollment token only for TOTP setup and verification.

Hosted sign-in can return a single-use handoff code to an allowlisted callback; redeem it from trusted server code with @zkauth/node, then create your own app session cookie.

The hosted account security helper can use the bearer token in component memory for profile, account stats, recent activity, sessions, devices, recovery, TOTP, MFA, and hosted-origin passkey helper controls.

Recovery-code export uses shown-once values already in memory.
Session-wide termination of other active sessions requires a fresh first-factor engine session.
Hosted recovery request, reset-completion, and passkey verification helper pages are narrower scoped surfaces.

Server machine tokens

@zkauth/node exposes introspectZKAuthMachineToken() and client.introspectMachineToken() for trusted server code.

  • They introspect the configured project API key as an opaque machine token.
  • They return sanitized client and key metadata.
  • They accept only optional issuer/audience assertions.

Opaque-only today

Machine-token support is project API-key self-introspection. It is not OAuth client credentials, JWT bearer-token issuance, local JWT verification, or user-created API keys.

Integration quickstarts

Choose the package for the boundary you own. Browser code uses app or hosted proxy routes. Server code can verify sessions and inject project keys without exposing them to users.

# Direct SDK or server workernpm install zkauth-clientimport { ZKAuthSDK } from "zkauth-client"
boundaryServer-side direct API key, or browser hosted proxy mode.

Authentication

register(params)

Derives a salt and commitment from the password on the client and registers the user. deviceInfo is required.

ts
const res = await zkauth.register({  email: 'ada@example.com',  password: 'SecurePassword123!',           // min 8 chars, stays on device  deviceInfo: { deviceName: 'Chrome on Mac', deviceType: 'desktop' },})// res.data -> { userId, email, emailVerified, verificationToken?, deviceId }

login(params)

Fetches the salt, generates a Groth16 proof locally, and exchanges it for a session. The token is returned and also stored on the instance.

ts
const res = await zkauth.login({  email: 'ada@example.com',  password: 'SecurePassword123!',  deviceInfo: { deviceName: 'Chrome on Mac', deviceType: 'desktop' },})// res.data -> { user, session, mfaRequired?, deviceApprovalRequired? }const token = res.data.session.token

Session helpers

ts
await zkauth.getCurrentUser()  // -> User | nullzkauth.isAuthenticated()       // -> booleanzkauth.getSession()            // -> Session | nullawait zkauth.logout()          // clears the in-memory session

verifyEmail(token) / getSalt(email)

ts
// register() may return data.verificationToken in devawait zkauth.verifyEmail(token)

Password reset

A reset can require approval depending on your project’s policy.

ZKAuth can start the request from a project-bound hosted forgot-password page and complete the token-based reset from the hosted reset-password page without exposing project API keys to browser code.

  • Hosted reset completion derives verifier material in the browser and does not create an application session.
  • Your app can still handle reset callbacks and call resetPassword directly when you own the reset UI.
ts
await zkauth.forgotPassword({ email: 'ada@example.com' })await zkauth.verifyResetToken(token)await zkauth.resetPassword({ token, newPassword: 'NewSecret123!', email })

Devices

List, trust, verify, and remove devices for the authenticated user.

ts
await zkauth.getDevices()                       // -> Device[]await zkauth.registerDevice({ deviceInfo })     // trust a new deviceawait zkauth.verifyDevice({ deviceId, proof, publicSignals, proofNonce, proofTimestamp })await zkauth.removeDevice(deviceId)

MFA

getMFAStatus() -> { mfaEnabled, totpEnabled, backupCodesGenerated, availableMethods }.

ts
import { ZKAuthError, ZKAuthErrorCode } from 'zkauth-client'
try {  await zkauth.login({ email, password, deviceInfo })} catch (e) {  if (e instanceof ZKAuthError && e.code === ZKAuthErrorCode.MFA_REQUIRED) {    await zkauth.verifyMFA({      token: e.details.token,      code: '123456',      codeType: 'totp', // or 'backup'    })  }}

TOTP setup, backup-code generation, session management, and the transparency endpoints aren’t wrapped by the client yet; call them over the HTTPS API with your session token.

OPAQUE & WebAuthn helpers

Thin wrappers over the engine’s OPAQUE and WebAuthn endpoints, for building those browser flows through your proxy:

  • opaqueStatus(), opaqueRegistrationResponse(), opaqueRegistrationFinish(), opaqueLoginStart(), opaqueLoginFinish()
  • webAuthnRegistrationOptions(), webAuthnRegistrationVerify(), webAuthnAuthenticationOptions(), webAuthnAuthenticationVerify()

Events

Subscribe to lifecycle events: login, logout, register, session_expired, mfa_required, device_approval_required, error.

ts
const off = zkauth.on('login', (user) => console.log('signed in', user.email))zkauth.on('session_expired', () => redirectToLogin())off() // unsubscribe

Errors

Failures throw a ZKAuthError with a code from ZKAuthErrorCode (e.g. AUTHENTICATION_ERROR, VALIDATION_ERROR, SESSION_EXPIRED, UNAUTHORIZED).

ts
import { ZKAuthError, ZKAuthErrorCode } from 'zkauth-client'
try {  await zkauth.login({ email, password })} catch (e) {  if (e instanceof ZKAuthError && e.code === ZKAuthErrorCode.AUTHENTICATION_ERROR) {    // wrong credentials / failed proof  }}

Wire it into a framework in Examples.