Reference
SDK reference
The published zkauth-client@2.0.0-beta.5 package exportsZKAuthSDK and runs Argon2id, Poseidon, and Groth16 proof generation in the client process. The Proof V2 path uses explicit development artifacts, is not production approved, and keeps the returned session in memory.
Client and framework helpers
The checked-out zkauth-client beta is the crypto-capable JavaScript client for Proof V2 registration, login, request-bound QR pairing, and the Access Pass experiment.
@zkauth/node, @zkauth/nextjs, @zkauth/express, and @zkauth/hono.@zkauth/react for app-owned React flows.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.
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})import { ZKAuthSDK } from 'zkauth-client'
const zkauth = new ZKAuthSDK({ hostedProxy: { baseUrl: 'https://zkauth.dev', projectSlug: 'your-project-slug', clientId: 'your_public_client_id', },})
const options = await zkauth.webAuthnAuthenticationOptions({ userId })// Your browser UI converts options and runs navigator.credentials.get(...).const login = await zkauth.webAuthnAuthenticationVerify({ userId, response: credentialResponse, deviceInfo,})import { preloadProofArtifacts, ZKAuthSDK } from 'zkauth-client'
const artifacts = { wasm: process.env.AUTH_V2_WASM_URL!, zkey: process.env.AUTH_V2_ZKEY_URL!,}
const zkauth = new ZKAuthSDK({ apiKey: process.env.ZKAUTH_API_KEY!, baseUrl: process.env.ZKAUTH_BASE_URL ?? 'http://127.0.0.1:3000', experimentalProofV2Artifacts: artifacts,})
await preloadProofArtifacts(artifacts)await zkauth.register({ email, password, deviceInfo })// Complete email verification before login.await zkauth.login({ email, password, deviceInfo })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))}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')}| Option | Type | Notes |
|---|---|---|
apiKey | string | Required for direct API mode. Keep it server-side and omit it when hostedProxy is configured. |
baseUrl | string | Engine URL for direct API mode. Defaults to the hosted engine. |
timeout | number | Request timeout in ms (default 30000). |
debug | boolean | Verbose logging. |
clientId | string | Optional; 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. |
experimentalAccessPassPublicClientId | string | Enables keyless public-client Access Pass challenge, proof submission, and introspection. It cannot be combined with an API key or hosted proxy and cannot enroll credentials or fetch paths. |
experimentalAccessPassArtifacts | { wasm, zkey } | Explicit development-only Access Pass artifacts. The proof path fails closed when they are omitted. |
experimentalProofV2Artifacts | { wasm, zkey } | Required for password-proof login and QR pairing. Development/research only; the package does not ship proving keys and fails closed when these artifacts are omitted. |
Hosted proxy boundary
The hosted proxy does not support Access Pass. It cannot preserve the relying application's exact Origin, so adding Access Pass routes to that allowlist would bind the ceremony to the wrong audience.
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 registration, safe metadata inventory, and revocation. Passkey mutations require a recent verified sign-in. Revocation attempts a credential-free email after deletion without making deletion depend on provider availability.
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-client@betaimport { ZKAuthSDK } from "zkauth-client"Authentication
register(params)
Derives a salt and commitment from the password on the client and registers the user. deviceInfo is required.
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)
Requests a short-lived V2 ceremony, generates a Groth16 proof in the client process, and exchanges it for a session through an atomic one-time consume. The token is returned and also stored on the instance.
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.tokenExperimental proof artifacts
Session helpers
await zkauth.getCurrentUser() // -> User | nullzkauth.isAuthenticated() // -> booleanzkauth.getSession() // -> Session | nullawait zkauth.logout() // clears the in-memory sessionverifyEmail(token) / getSalt(email)
// register() may return data.verificationToken in devawait zkauth.verifyEmail(token)Request-bound QR pairing
A pending QR request is exchanged for a short-lived Proof V2 ceremony bound to that exact request. Completion verifies the client proof and atomically consumes both one-time records before creating device and session state. It does not fall back to the legacy proof flow.
const ceremony = await zkauth.issuePairingChallenge({ pairingToken, email,})
const pairing = await zkauth.completePairing({ pairingToken, email, password, deviceInfo: { ...deviceInfo, deviceFingerprint }, ceremony,})
if (pairing.outcome !== 'authenticated') { // Continue with pairing.token and pairing.availableMethods. // Pairing never marks MFA verified by itself.}Pairing is a first factor
mfaVerified: false. Handle mfa_required andmfa_enrollment_required as continuations, never as an authenticated application session.Experimental ZK Access Pass
Access Pass is a separate anonymous-authorization experiment. It proves current tenant Merkle membership and a hidden role threshold for one exact origin and action, then returns a short-lived opaque capability and pairwise pseudonym without returning identity fields.
// Trusted issuer backend: the issuer controls the role and provisions// issuerGeneratedFieldSecret to the holder through its secure workflow.const projectSdk = new ZKAuthSDK({ apiKey: loadServerOnlyProjectKey(), clientId: publicClientId, baseUrl: 'http://127.0.0.1:3000',})const enrollment = await projectSdk.enrollAccessCredential({ organizationId, credentialSecret: issuerGeneratedFieldSecret, roleTier: issuerAssignedRoleTier, credentialVersion: 1,})const context = await projectSdk.requestAccessPassProveContext({ organizationId, credentialId: enrollment.credentialId,})
// Separate browser/public client: no project API key.const publicSdk = new ZKAuthSDK({ baseUrl: 'http://127.0.0.1:3000', experimentalAccessPassPublicClientId: publicClientId, experimentalAccessPassArtifacts: { wasm: wasmUrl, zkey: zkeyUrl },})const ceremony = await publicSdk.requestAccessPassChallenge({ organizationId, audience: window.location.origin, action: 'reports:read', minimumRoleTier: 2,})const proof = await publicSdk.generateAccessPassProof({ organizationId, credentialId: enrollment.credentialId, credentialSecret: issuerGeneratedFieldSecret, roleTier: issuerAssignedRoleTier, credentialVersion: 1, ceremony, context,})const capability = await publicSdk.verifyAccessPass({ organizationId, audience: window.location.origin, ceremony, ...proof,})Issuer and deployment boundary
The credential issuer must control the role embedded in the enrolled leaf. Never accept a holder-selected role during enrollment.
This surface is included in the published SDK beta and still requires a development engine plus development-only artifacts. It is not deployed, production approved, independently audited, or a replacement for login, MFA, federation, recovery, or application sessions.
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
resetPassworddirectly when you own the reset UI.
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.
await zkauth.getDevices() // -> Device[]const device = await zkauth.registerDevice({ deviceInfo }) // keep device.deviceSecret securelyawait zkauth.verifyDevice({ deviceId, deviceSecret })await zkauth.removeDevice(deviceId)MFA
getMFAStatus() -> { mfaEnabled, totpEnabled, backupCodesGenerated, availableMethods }.
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
OPAQUE remains a thin protocol transport. The WebAuthn helpers support policy-bound sign-in for a known user ID and authenticated passkey registration through your proxy:
opaqueStatus(),opaqueRegistrationResponse(),opaqueRegistrationFinish(),opaqueLoginStart(),opaqueLoginFinish()webAuthnRegistrationOptions(),webAuthnRegistrationVerify(),webAuthnAuthenticationOptions(),webAuthnAuthenticationVerify()
Call webAuthnRegistrationOptions() and webAuthnRegistrationVerify() only after authentication; the SDK sends the in-memory bearer session, and the engine ignores body-supplied user or tenant identity. A successful webAuthnAuthenticationVerify() stores the new session in SDK memory and preserves MFA, device-approval, and enrollment continuations as typed errors.
Events
Subscribe to lifecycle events: login, logout, register, session_expired, mfa_required, device_approval_required, error.
const off = zkauth.on('login', (user) => console.log('signed in', user.email))zkauth.on('session_expired', () => redirectToLogin())off() // unsubscribeErrors
Failures throw a ZKAuthError with a code from ZKAuthErrorCode (e.g. AUTHENTICATION_ERROR, VALIDATION_ERROR, SESSION_EXPIRED, UNAUTHORIZED).
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.