@zoza/auth
v0.1.0
Published
Zoza Auth SDK. Phishing-resistant device authentication using per-device Curve25519 key pairs + signed challenges. Replaces SMS OTP and TOTP with a one-tap approve on the user's already-registered device — no shared secrets, no SIM-swap risk.
Maintainers
Readme
@zoza/auth
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.
Install
npm install @zoza/authWhy
| | 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 |
Quick start (server side)
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', ... }Quick start (device side)
// 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.
API
new AuthClient({ apiKey, apiUrl?, fetch? })
| 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. |
Methods
| 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 |
Tests
npm install
npm testLicense
MIT © Zoza
