Device-keypair authentication. Replaces SMS OTP and TOTP with a one-tap approve on the user's already-registered device. No shared secrets. No SIM-swap risk. Per-challenge metadata shows users exactly what they're approving.
npm install @zoza/auth| SMS OTP | TOTP (Google Authenticator) | Zoza Auth | |
|---|---|---|---|
| Shared secret on the server | ❌ | ❌ | ✅ none (device holds private key) |
| SIM-swap survivable | ❌ | ✅ | ✅ |
| Shows what is being approved | ❌ | ❌ | ✅ per-challenge metadata |
| Phishing-resistant | ❌ | ❌ | ✅ signature bound to nonce |
| SMS cost per login | ₹0.10-0.25 | ₹0 | ~₹0 |
import { AuthClient } from '@zoza/auth';
const auth = new AuthClient({ apiKey: process.env.ZOZA_AUTH_KEY! });
// 1. Register each user's device at enrolment
await auth.registerDevice({
user_id: 'user_123',
device_id: 'dev_iphone15_abc',
device_name: 'Rahul — iPhone 15',
public_key: deviceCurve25519HexFromEnrollmentFlow,
});
// 2. Issue a challenge whenever you need an auth
const challenge = await auth.issueChallenge({
user_id: 'user_123',
context: 'payment',
metadata: 'Transfer ₹5000 to Rahul Sharma',
ttl: 30,
});
// push challenge.id to the user's device via your existing push/WebSocket
// 3. Poll for the signed response (or use v0.2 webhooks when they land)
const result = await auth.getChallengeStatus(challenge.id);
// → { status: 'approved' | 'rejected' | 'expired' | 'pending', ... }// On the user's already-registered device:
import { AuthClient } from '@zoza/auth';
const auth = new AuthClient({ apiKey: 'unused-for-device-endpoints' });
// Fetch the challenge details (public endpoint — ID is the capability)
const details = await auth.getChallengeDetails(challengeId);
// Show user details.metadata; let them approve/reject.
// Sign and submit
await auth.respondChallenge(challengeId, {
device_id: 'dev_iphone15_abc',
client_pub: ephemeralPubHex,
proof: sigHex,
approved: true,
});The proof is produced with the device's Curve25519 private key over (nonce || challenge.id || client_pub || approved). See the Auth whitepaper for the exact derivation.
| Option | Type | Notes |
|---|---|---|
apiKey |
string (required) |
Issued at zoza.world/developers/auth. Format auth_<base64>. |
apiUrl |
string |
Default https://auth-api.zoza.world. |
fetch |
typeof fetch |
Optional — Node <18 or custom signers. |
| Method | Auth | Purpose |
|---|---|---|
registerDevice({...}) |
API key | Add a device public key under a user |
issueChallenge({...}) |
API key | Mint a challenge for a user |
getChallengeStatus(id) |
API key | Poll challenge result |
getChallengeDetails(id) |
public | Device-side: fetch metadata to display |
respondChallenge(id, {...}) |
public | Device-side: submit signed response |
getAppAudit(appId) |
API key | Tamper-evident app audit log |
npm install
npm testMIT © Zoza