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.
@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: { projectSlug: 'your-project-slug', clientId: 'your_public_client_id', },})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. |
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.
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"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)
Fetches the salt, generates a Groth16 proof locally, and exchanges it for a session. 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.tokenSession 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)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[]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 }.
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.
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.