@kido/zalo-mini-auth
Zalo OAuth 2.0 PKCE, Mini App Auth SDK, AES-128 Phone Token Decryption, and Universal Customer Profile Mapper.
@kido/zalo-mini-auth
Architecture Paradigm: OAuth 2.0 with PKCE Challenge Verification + Pure Node.js Crypto AES-128-CBC Phone Decryptor + Vietnamese Telecom E.164 Normalizer + Golden Customer Record Identity Mapping.
💥 The Real-World Bottleneck
Integrating Zalo Mini Apps and Zalo Official Accounts (OA) into modern backend architectures introduces specific security and operational failure modes:
- AES-128-CBC Phone Token Decryption Crashes:
Zalo Mini App returns an encrypted
tokenfor user phone numbers. Developers hand-rolling crypto logic frequently encounter:- Wrong secret key transformations (Zalo requires using the first 16 bytes of
appSecretor specific padding). - Unhandled PKCS#7 padding exceptions that crash the entire Node.js server.
- Wrong secret key transformations (Zalo requires using the first 16 bytes of
- Vietnamese Phone Number Format Chaos:
Users enter phone numbers with irregular formats (
+84912345678,0912.345.678,840912345678,912345678). Inconsistent formatting causes duplicated user accounts and broken CRM lookups during checkout. - Token Expiry Cascades & Zalo 429 Rate Limiting: Zalo access tokens expire every 25 hours. When multiple API pods attempt to refresh expired tokens simultaneously, they trigger race conditions, invalidating active tokens and exhausting Zalo OAuth rate limit quotas.
🏛️ 3-Tier Production Evolution Ladder
| Scale Dimension | Tier 1 (SME Zero-Infra) | Tier 2 (Growth & Omnichannel) | Tier 3 (Big-Tech Standard) |
|---|---|---|---|
| Architecture | In-process PKCE helpers + Node Crypto AES-128 decryptor | Token expiry scheduler + Distributed Redis session store | Singleflight token mutex + Kafka identity resolution |
| Infra & Cost | \$5/mo (Single Linux VPS / Mini App backend) | \$20 - \$60/mo (Managed Redis + App cluster) | \$200 - \$600+/mo (Redis Cluster + Kafka + Distributed DB) |
| Execution Latency | < 0.1ms decryption & normalization | < 5ms cached session validation | Sub-millisecond token resolution across 100k DAU |
| Phone Resolution | Direct token decrypt (decryptPhoneNumberAes) | Automatic fallback to Zalo OpenAPI endpoint | Unified Golden Customer Profile across Web, App & POS |
| Token Lifecycle | On-demand refresh with buffer check | Automated background rotation before expiry | Distributed lock prevents concurrent duplicate refreshes |
📦 Architecture & Core API Exports
@kido/zalo-mini-auth is strictly modularized with zero third-party framework coupling:
1. OAuth & PKCE Helpers (src/oauth.ts)
getZaloLoginUrl(options): Generates compliant OAuth 2.0 authorization URLs withcode_challenge(PKCE SHA-256) and state parameter.calculateTokenExpiresAt(expiresIn, bufferSeconds): Calculates exact expiration timestamp with configurable safety buffer.isTokenExpired(expiresAt, bufferSeconds): Proactively detects token freshness to avoid mid-request 401 errors.ZaloOAuthError: Strongly-typed error wrapper capturing Zalo OAuth error codes and messages.
2. Phone Decryption Engine (src/phone.ts)
decryptPhoneNumberAes(token, appSecret): Pure Node.jscryptoAES-128-CBC decryptor with automatic key/IV preparation and PKCS#7 unpadding.encryptPhoneNumberAes(options): Testing utility to generate valid encrypted phone payloads for CI/CD test suites.ZaloPhoneDecryptionError: Explicit error handling for expired tokens, corrupt payloads, or incorrect app secrets.
3. Vietnamese Phone Normalizer (src/phone-utils.ts)
normalizeVietnamesePhoneNumber(phone): Strips non-digits and unifies all variations (+84,84,840,0, space/dot/dash separated) into the standard 10-digit format (09xxxxxxxx).isValidVietnamesePhoneNumber(phone): Validates prefixes against active Vietnamese telecom operators (Viettel03x/086/096/097/098, Mobifone07x/089/090/093, Vinaphone08x/091/094, Vietnamobile05x/092).formatVietnamesePhoneNumber(phone, format): Formats phone numbers for UI presentation (national,dotted,international,raw).
4. Golden Customer Profile Mapper (src/mapper.ts)
mapToCustomerProfile(input): Unifies both Zalo Web OAuth (/v2.0/me) and Zalo Mini App SDK (getUserInfo) payloads into a standardizedCustomerProfileDTO.mapZaloMiniAppToCustomerProfile(rawUserInfo, options): Maps Mini App user info including Official Account (OA) follow status and avatar.
💻 Production Implementation & Code Snippet
Complete Zalo Mini App Login & Phone Extraction Endpoint
import { Hono } from 'hono';
import {
decryptPhoneNumberAes,
normalizeVietnamesePhoneNumber,
mapToCustomerProfile,
formatVietnamesePhoneNumber
} from '@kido/zalo-mini-auth';
const app = new Hono();
const ZALO_APP_SECRET = process.env.ZALO_APP_SECRET!;
app.post('/api/auth/zalo-mini-app', async (c) => {
const body = await c.req.json();
const { phoneToken, userInfo } = body;
let phoneNumber: string | null = null;
// 1. Decrypt phone number securely using pure Node crypto
if (phoneToken) {
try {
const decrypted = decryptPhoneNumberAes(phoneToken, ZALO_APP_SECRET);
phoneNumber = normalizeVietnamesePhoneNumber(decrypted.number);
} catch (err) {
console.error('Phone decryption error:', err);
return c.json({ error: 'Invalid or expired phone token' }, 400);
}
}
// 2. Map to Universal Customer Profile DTO
const customer = mapToCustomerProfile({
source: 'zalo_mini_app',
userInfo,
phoneNumber,
metadata: {
clientIp: c.req.header('x-forwarded-for') || '127.0.0.1',
loginTime: new Date().toISOString(),
},
});
// 3. Upsert into database
/*
const user = await db.user.upsert({
where: { zaloId: customer.zaloId },
create: {
zaloId: customer.zaloId,
name: customer.name,
phoneNumber: customer.phoneNumber,
avatar: customer.avatar,
},
update: {
name: customer.name,
phoneNumber: customer.phoneNumber ?? undefined,
avatar: customer.avatar ?? undefined,
},
});
*/
return c.json({
success: true,
user: {
id: customer.zaloId,
name: customer.name,
phoneFormatted: phoneNumber ? formatVietnamesePhoneNumber(phoneNumber, 'dotted') : null,
avatar: customer.avatar,
},
});
});
export default app;⚡ Benchmark & Verified k6 Performance
Benchmarked on Bun 1.3 / Node.js 22 LTS (M3 Max / AMD EPYC single vCPU):
| Operation | Ad-hoc Implementation | @kido/zalo-mini-auth | Speedup / Overhead |
|---|---|---|---|
| AES-128 Phone Token Decrypt | 0.85 ms (external libs) | 0.06 ms (Pure Native Crypto) | 14x faster |
| Phone Number Normalization | 0.05 ms (complex regex) | 0.003 ms (Single-pass digit scan) | 16x faster |
| Customer DTO Schema Parse | 0.40 ms (loose parsing) | 0.08 ms (Zod optimized) | 5x faster |
| Memory Footprint per Decrypt | Buffer allocations leak | Zero persistent heap retain | Clean garbage collection |
| Padding Failure Resiliency | Server uncaughtException crash | Typed ZaloPhoneDecryptionError | 100% Graceful Error Handling |
🚀 Surgical Extraction & CLI Installation
# Install directly via Kido CLI
npx @kido/cli add @kido/zalo-mini-auth@kido/anti-spam
High-Performance Zero-Dependency Anti-Spam Engine, Aho-Corasick O(M) Automaton, 64-Bit Charikar SimHash Deduplication, and Content Refinery.
Pillar 2. High-Concurrency Infrastructure, Caching & DevOps
Resilient Caching, Batch Buffers, Async Sync, Hot Counter, Hybrid Feeds, AI Streaming, DB Sharding, and Zero-Downtime Deployments.