Authentication
Authentication
Section titled “Authentication”z0 provides API key parsing and tenant extraction, but does not verify keys against a database. This is intentional—the SDK is generic and doesn’t prescribe how you store or verify credentials.
What z0 Provides
Section titled “What z0 Provides”The authenticateRequest() function in @z0-app/sdk:
- Extracts the
X-API-Keyheader - Validates the key format:
<prefix>_<mode>_<tenant_id>_<random> - Returns the parsed components (mode, tenant_id, apiKey)
import { authenticateRequest } from '@z0-app/sdk';
export async function handleRequest(request: Request) { const authResult = authenticateRequest(request, 'myapp');
if (!authResult.success) { return new Response(authResult.error, { status: 401 }); }
const { tenantId, mode, apiKey } = authResult.context; // Now you must verify the key...}What You Must Implement
Section titled “What You Must Implement”You are responsible for:
1. API Key Storage
Section titled “1. API Key Storage”Store API keys securely. Options include:
- KV Namespace: Fast lookups, good for high-volume APIs
- D1 Database: Relational storage with metadata
- External Auth Service: Auth0, Clerk, or custom service
Example KV schema:
Key: apikey:<hash_of_key>Value: { tenant_id: "...", created_at: ..., scopes: [...] }2. Key Verification
Section titled “2. Key Verification”After parsing, verify the key exists and is valid:
async function verifyApiKey(apiKey: string, env: Env): Promise<boolean> { // Hash the key for lookup (never store plain keys) const keyHash = await hashApiKey(apiKey);
// Look up in your storage const record = await env.API_KEYS.get(keyHash); if (!record) return false;
const data = JSON.parse(record);
// Check if key is revoked if (data.revoked_at) return false;
// Check if key has expired if (data.expires_at && data.expires_at < Date.now()) return false;
return true;}3. Tenant ID Verification
Section titled “3. Tenant ID Verification”Ensure the tenant_id in the key matches a real tenant:
async function verifyTenant(tenantId: string, env: Env): Promise<boolean> { // Check against your tenant registry const tenant = await env.TENANTS.get(tenantId); return tenant !== null;}4. Mode Enforcement
Section titled “4. Mode Enforcement”The mode field (live/test) should affect behavior:
if (authResult.context.mode === 'test') { // Route to test data, sandbox environment // Use mock external services // Apply lower rate limits}Complete Example
Section titled “Complete Example”import { authenticateRequest, type AuthContext } from '@z0-app/sdk';
interface Env { API_KEYS: KVNamespace; TENANTS: KVNamespace;}
export async function authenticate( request: Request, env: Env): Promise<AuthContext | Response> { // Step 1: Parse the API key const authResult = authenticateRequest(request, 'myapp'); if (!authResult.success) { return new Response(JSON.stringify({ error: authResult.error }), { status: 401, headers: { 'Content-Type': 'application/json' } }); }
const { apiKey, tenantId, mode } = authResult.context;
// Step 2: Verify the key exists and is valid const keyHash = await hashApiKey(apiKey); const keyRecord = await env.API_KEYS.get(keyHash);
if (!keyRecord) { return new Response(JSON.stringify({ error: 'Invalid API key' }), { status: 401, headers: { 'Content-Type': 'application/json' } }); }
const keyData = JSON.parse(keyRecord);
if (keyData.revoked_at) { return new Response(JSON.stringify({ error: 'API key revoked' }), { status: 401, headers: { 'Content-Type': 'application/json' } }); }
// Step 3: Verify tenant exists const tenant = await env.TENANTS.get(tenantId); if (!tenant) { return new Response(JSON.stringify({ error: 'Unknown tenant' }), { status: 401, headers: { 'Content-Type': 'application/json' } }); }
// Step 4: Verify key belongs to this tenant if (keyData.tenant_id !== tenantId) { return new Response(JSON.stringify({ error: 'Key/tenant mismatch' }), { status: 401, headers: { 'Content-Type': 'application/json' } }); }
// Success - return the auth context return authResult.context;}
async function hashApiKey(key: string): Promise<string> { const encoder = new TextEncoder(); const data = encoder.encode(key); const hashBuffer = await crypto.subtle.digest('SHA-256', data); const hashArray = Array.from(new Uint8Array(hashBuffer)); return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');}Security Considerations
Section titled “Security Considerations”Never Store Plain API Keys
Section titled “Never Store Plain API Keys”Always hash keys before storage:
// Good: Store hashconst hash = await hashApiKey(plainKey);await env.API_KEYS.put(hash, JSON.stringify(metadata));
// Bad: Store plain keyawait env.API_KEYS.put(plainKey, JSON.stringify(metadata)); // DON'TUse Constant-Time Comparison
Section titled “Use Constant-Time Comparison”When comparing keys or hashes, use constant-time comparison to prevent timing attacks:
import { timingSafeEqual } from 'crypto';
function secureCompare(a: string, b: string): boolean { const bufA = Buffer.from(a); const bufB = Buffer.from(b); if (bufA.length !== bufB.length) return false; return timingSafeEqual(bufA, bufB);}Implement Rate Limiting
Section titled “Implement Rate Limiting”Protect against brute-force attacks:
const rateLimitKey = `ratelimit:auth:${clientIP}`;const attempts = await env.RATE_LIMITS.get(rateLimitKey);
if (attempts && parseInt(attempts) > 10) { return new Response('Too many attempts', { status: 429 });}Log Authentication Events
Section titled “Log Authentication Events”Create Facts for security auditing:
await ledger.appendFact({ type: 'auth', subtype: success ? 'login_success' : 'login_failure', tenant_id: tenantId, data: { ip: request.headers.get('CF-Connecting-IP'), user_agent: request.headers.get('User-Agent'), key_prefix: apiKey.substring(0, 10) + '...' }});API Key Format
Section titled “API Key Format”The standard format is:
<prefix>_<mode>_<tenant_id>_<random>| Component | Description |
|---|---|
prefix | Your app identifier (default: z0) |
mode | live or test |
tenant_id | The tenant this key belongs to |
random | Cryptographically random suffix |
Example: myapp_live_acme_corp_7f3a9b2c1d4e5f
Generating Keys
Section titled “Generating Keys”function generateApiKey(prefix: string, mode: string, tenantId: string): string { const random = crypto.randomUUID().replace(/-/g, '').substring(0, 16); return `${prefix}_${mode}_${tenantId}_${random}`;}Integration Patterns
Section titled “Integration Patterns”Workers for Platforms
Section titled “Workers for Platforms”When using Workers for Platforms, verify keys at the dispatch worker level:
// Dispatch Workerexport default { async fetch(request: Request, env: Env) { const auth = await authenticate(request, env); if (auth instanceof Response) return auth;
// Route to tenant's worker const userWorker = env.DISPATCHER.get(auth.tenantId); return userWorker.fetch(request); }}Service Binding Authentication
Section titled “Service Binding Authentication”For internal service-to-service calls, use a different authentication mechanism (e.g., signed tokens or mutual TLS) rather than API keys.