Core concepts
Security
The point of ZKAuth is that a server compromise can’t hand an attacker your users’ passwords, because the server never had them. Here’s how that holds up, and where the boundaries are.
Cryptography
Password authentication is built on zero-knowledge proofs rather than stored password hashes:
- Groth16 zk-SNARKs over the BN128 curve verify knowledge of the password-derived secret.
- Poseidon hashing inside the circuit, with Argon2id for key-stretching at the edges.
- The engine stores a verifier value, not the password, and not a reversible hash of it.
Where the verifier lives
Replay & tamper protection
- Proof freshness: each proof is single-use. Replays are rejected on the hot path (backed by a Redis freshness record).
- Tamper rejection: malformed proof coordinates and mismatched public signals are rejected before a login is accepted.
- Device binding:
deviceInfoties an attempt to a device fingerprint; mismatches can be challenged.
Tenant isolation & keys
- Keys are tenant-bound: a
zka_live_orzka_test_key only acts on its own project. - Bearer-only requests (no API key) and invalid keys are rejected.
- Rate limits apply per client to blunt automated abuse.
- API-key audit entries and security events give you a trail to inspect.
- Project API keys can be introspected as opaque machine tokens from trusted server code. The introspection route accepts issuer/audience assertions, inherits API-key rate and quota checks, records a machine-auth security event, and rejects raw token fields in the request body.
- Machine-token introspection is not OAuth client credentials or JWT machine-token issuance.
- End-user API keys are separate
zka_user_opaque credentials bound to one project and user. Creation returns the secret once from dashboard admin or hosted account helper flows and stores only HMAC material. - End-user keys require scopes and expiry, support revocation and last-used metadata, and validate only from trusted server code under the calling project API key.
Validate a key any time:
curl https://api.zkauth.dev/api/v1/client/me \ -H "x-api-key: zka_live_<project_key>"Organizations & federation boundary
- Preview organization support is tenant-scoped to a project. Profiles, memberships, roles, invitations, teams, verified domains, organization webhooks, and audit events stay bound to the authenticated client.
- Organization invitations use hashed single-use tokens and expire or revoke before membership is accepted.
- DNS domain verification records are checked before a domain is marked verified. OAuth/OIDC and SCIM attachments must resolve through an active verified-domain record for the same project and active organization when domain attachment is required.
- ZKAuth ships preview OAuth/OIDC federation foundations, dashboard provider setup, and organization-scoped SCIM Users, Groups, signed cursor pagination, bounded Bulk writes, and reveal-once SCIM token issuance.
- SAML engine groundwork exists internally, but SAML is planned for a later enterprise release and is not public-launch enabled.
- ZKAuth does not yet ship certified enterprise SSO, certified SCIM directory sync, real-provider certification, or per-seat billing.
Callbacks & hosted helpers
Email links, templates, and result pages
- Email verification, new-device approval/denial, and password reset links are handled by ZKAuth first.
- Project email templates are bounded text-only subject/body overrides. The engine still controls the HTML shell, required action buttons, fallback links, escaping, and plain-text alternatives.
- Hosted email verification and device approval/denial result pages show constrained status copy after the engine processes the single-use link. They do not show raw upstream error text, do not trust arbitrary query parameters as authentication proof, and do not create application sessions.
- Device approval and denial use separate single-use links. The engine records the first valid decision and rejects later reuse.
Hosted sign-up and sign-in
- Project-bound hosted sign-up/sign-in helper pages can register and verify engine authentication without exposing project API keys to the browser. Hosted sign-up sends only salt plus commitment.
- Hosted sign-in sends proof/public signal material and can show MFA, MFA enrollment-required, or device-approval-required states. If project-required MFA is enabled for a user without a usable factor, the engine returns a short-lived enrollment-only token after first-factor proof verification. Hosted sign-in can use that token only for TOTP setup and verification before receiving a fresh MFA-backed engine session.
- After a successful engine sign-in, hosted sign-in can issue a short-lived, single-use handoff code to the project’s allowlisted callback. The developer app redeems that code from trusted server code and creates its own session cookie.
Account security helper
- After successful hosted engine sign-in, the account security helper keeps the bearer token in component memory only. It does not write the token to browser storage, cookies, callback URLs, or page copy.
- It can load profile, account statistics, recent login activity, session, device, MFA, and recovery-code views through bearer-gated hosted proxy endpoints.
- It can also drive TOTP setup/reset, MFA disable, hosted-origin passkey, and user API-key helper controls. IP and browser metadata shown by the helper is bounded before display.
- Terminating other sessions or creating/revoking user API keys through this helper requires a fresh first-factor engine session.
- Recovery-code generation, MFA backup-code generation, and MFA disable are guarded by the engine’s fresh MFA-backed sensitive-session middleware.
- Recovery-code export uses only the shown-once plaintext codes already held in component memory.
- When TOTP verification returns a fresh MFA-backed engine session, the hosted account helper replaces only the in-memory bearer token and refreshes account state.
- If the current engine session does not satisfy a guard, hosted UI shows a bounded error instead of returning plaintext codes or changing factors.
- Hosted passkey and account-helper failures are mapped from response status or browser ceremony state to bounded public copy; raw backend messages, request paths, and diagnostic codes stay out of the hosted end-user UI.
Recovery and passkeys
- Project-bound hosted forgot-password and reset-password completion pages can run recovery without exposing project API keys to the browser. Hosted reset completion verifies the token through the hosted proxy, derives verifier material in the browser, and sends only the token, salt, and commitment.
- A project-bound hosted passkey helper can verify an already registered credential for a known user ID through the hosted proxy when the credential was registered for the hosted WebAuthn origin. It is not a full hosted passkey sign-in or account-management page.
- The hosted account security helper can register a passkey only for the hosted WebAuthn origin. App-domain passkeys still require app-owned WebAuthn registration routes with matching relying-party settings.
Redirects, origins, and hardening
- Redirects only continue to an exact project allowlist match. Unknown or unsafe redirects fall back to hosted ZKAuth result pages.
- Callback configuration is normalized before storage, capped per project, HTTPS-required outside localhost development, and rejected if it points at private-network hosts.
- Allowed browser origins are stored separately from callback URLs. They are normalized as exact origins, capped per project, HTTPS-required outside localhost development, and rejected if they include wildcards, paths, query strings, credentials, or private-network hosts.
- Hosted pages and hosted proxy responses add no-store caching, clickjacking protection, and hosted-specific CSP with
frame-ancestors 'none'. - Current mutating hosted proxy routes reject cross-site browser request contexts using
Originand Fetch Metadata checks. A suppliedOriginmust match the hosted route exactly, and requests reportingSec-Fetch-Site: cross-siteorsame-siteare rejected; server-to-server requests without browser metadata remain supported. - Hosted config and proxy routes are rate-limited, return no-store responses, and vary hosted API responses on request-context headers so intermediaries cannot reuse decisions across origins.
- Developers should handle
zkauth_action,success,error, and hosted handoffcodeparameters on their own callback page. Treat the code as single-use server-side material, not browser session state.
Session cookies & hosted boundaries
Hosted helper pages do not set developer application session cookies. The developer app still owns its app-domain session after any hosted handoff.
The hosted handoff code is single-use server-side material. ZKAuth must never place the bearer session token directly in a callback URL.
Hosted helper errors use bounded public copy: backend messages and diagnostic codes stay out of hosted end-user UI unless they are explicitly mapped to safe text.
- Hosted pages can register an account or verify an engine sign-in.
- They can issue a single-use handoff code to an allowlisted callback.
- They can show MFA, device-approval, recovery, passkey, or result-page states.
- When your backend redeems a handoff code, keep the resulting session in a host-only cookie and never pass session tokens through URLs.
Project security policy controls the ZKAuth engine token lifetime, not your application cookie lifetime.
- Final sign-in sessions use the configured engine session lifetime.
- MFA-completed sessions, refresh rotations, and trusted QR pairing sessions are also bounded by engine policy.
- After hosted handoff redemption, the developer application still owns the app-domain cookie lifetime.
- Use
HttpOnly,Secure, andSameSite=LaxorSameSite=Strictfor app session cookies. UseSameSite=Noneonly when a real cross-site embed requires it, and only withSecure. - Prefer a host-only cookie without a broad
Domainattribute, so unrelated subdomains do not receive the app session. - Treat
SameSiteas defense in depth, not a replacement for CSRF tokens, Origin checks, Fetch Metadata checks, and high-risk re-authentication. - Regenerate the app session after login, logout, password reset, privilege changes, and other authentication state changes to avoid session fixation.
- ZKAuth-hosted sign-in hands back a single-use server-side code for the developer app to exchange. It must not place the bearer session token directly in a callback URL.
This guidance follows the OWASP session management and CSRF guidance plus the MDN cookie attribute reference.
Webhook safety
- Webhook secrets must stay server-side and should be rotated after exposure.
- Consumers should verify signatures over the raw request body and reject stale timestamps to prevent replay.
- Handlers should be idempotent because retries can deliver the same event more than once.
- ZKAuth records delivery attempts so developers can inspect response status, success, and retry behavior.
Operational best practices
- Keep keys server-side. Never ship a key in a client bundle; proxy browser flows through your backend.
- Use HTTPS everywhere. The API enforces it; your app should too.
- Keep origin allowlists exact. Configure each browser origin as scheme, host, and optional port only. Do not use wildcard origins.
- Separate environments. Test keys for dev/CI, live keys for production, rotated independently.
- Store sessions in http-only cookies, not local storage.
- Do not log secrets. This includes API keys, session JWTs, verification tokens, reset tokens, device approval tokens, webhook secrets, database URLs, and provider keys.
Account security controls
Beyond the login itself, the engine exposes per-user controls you can build into your account UI:
- TOTP MFA: setup, login challenge enforcement, status, single-use backup codes, hosted account-helper setup/reset, and fresh-session protection before MFA disable.
- Session management: list active sessions and revoke them individually or all-but-current.
- Trusted devices: list, trust-level update, and removal endpoints are scoped to the authenticated project before they touch device state or session revocation.
- Emergency reset: account recovery gated by a 72-hour delay, so a stolen inbox can’t instantly take over an account.
- Security events & alerts: a stream you can surface to users or operators.
Transparency & evidence
Security writing is easy to fake, so the engine lets you check rather than trust. These read-only endpoints report how it is actually configured:
/security/crypto-policy: the live cryptographic parameters (curve, hash, Argon2id settings)./security/standards: standards mappings, published without claiming certification./security/pq-readiness: an honest post-quantum readiness posture./security/evidenceand/security/assurance/policy: assurance records for operator and auditor review.
Why this exists
Troubleshooting
| Symptom | Likely cause |
|---|---|
| 401 / invalid key | Wrong or missing x-api-key, or a test key used against live data. Validate with GET /api/v1/client/me. |
| Login rejected for a valid password | A replayed or stale proof, or a device-fingerprint mismatch. Retry a fresh login; the client generates a new proof. |
| Passkey ceremony fails | WEBAUTHN_RP_ID / WEBAUTHN_ORIGIN don’t match the serving domain. See Deployment. |
| Slower hosted calls | Serverless cold starts, proxy work, and remote database latency. Budget user-facing timeouts accordingly. |
What we don’t claim
Security writing should be precise, so here is the honest boundary. As of today, ZKAuth does not claim:
- SOC 2, HIPAA, or any completed external audit or certification.
- No post-quantum security claim.
- No broad authenticator compatibility guarantee for WebAuthn.
Billing foundation exists for usage tracking, account plan records, checkout and portal routes, entitlement snapshots, and provider webhook verification.
Live provider activation, paid self-serve checkout, and production entitlement enforcement are not launched. We’ll update this page when that changes, not before.
Early access
zkauth-client are in active development. Pin a client version and review release notes before upgrading.