Derbent: Building a High-Performance SSO Engine on Cloudflare Workers
Managing authentication across multiple independent projects usually leads to “Auth Fragmentation”—each app has its own user table, its own session logic, and its own security vulnerabilities.
To solve this, I built Derbent: a self-hosted, centralized authentication engine designed specifically for the Cloudflare Workers ecosystem. It allows me to manage identity in one place for all my apps while leveraging Cloudflare’s global edge performance.
The Architecture: The “Sidecar” Pattern
Derbent acts as a sidecar service. Instead of the “Consuming App” (e.g., a dashboard or an OMR tool) managing passwords, it communicates with Derbent via Service Bindings. This allows for secure, low-latency communication that never touches the public internet.
Technical Stack
- Routing: Hono (Ultra-fast routing for the Edge)
- Database: Cloudflare D1 (SQL for Identity and Audit Logs)
- Session Store: Cloudflare KV (Stateful session management)
- Email: Cloudflare Queues + Resend (Moving to native Cloudflare Email soon)
Technical Deep Dive: Security & Persistence
1. Identity with UUIDv7
We use UUIDv7 for user IDs. Unlike standard UUIDs, v7 is time-ordered. This is crucial for D1 (SQLite) performance; time-ordered keys ensure that new rows are appended to the end of the B-Tree index, preventing “page fragmentation” and keeping writes fast as the database grows.
2. Stateful Sessions vs. JWTs
While JWTs are popular, they are difficult to revoke globally. Derbent uses opaque session tokens stored in KV.
To prevent Session Hijacking, we bind the session to the browser’s fingerprint (IP + User-Agent). If the fingerprint changes mid-session, we trigger a security alert and invalidate the session.
// Session verification logic with fingerprinting
async verifySession(sessionId, appId, clientIp, clientUa) {
const session = await this.sessionRepo.get(sessionId);
if (!session) throw new AppError('Session not found', 401);
// Hijack Prevention: UA Check
if (clientUa && session.userAgent !== clientUa) {
await this.auditLogRepo.log({
action: 'security_alert_hijack',
userId: session.userId,
ip: clientIp,
userAgent: clientUa
});
throw new AppError('Session invalid (Client Mismatch)', 401);
}
return session;
}
3. Password Hashing
We use PBKDF2 with a variable iteration count (default 100,000). The hash is stored in a iterations:salt:hash format, allowing us to increase the work factor for new passwords without breaking old ones.
Quality of Life (Developer Experience)
One of the main goals was making Derbent easy to deploy and integrate into new projects.
1-Click Deployment
I’ve integrated the Cloudflare Deploy Button. With one click, the repository is cloned, D1 and KV namespaces are provisioned, and migrations are applied automatically.
Environment-Driven Configuration
All service settings—from the password hashing strength to the SSO root domain—are managed via environment variables in wrangler.jsonc.
"vars": {
"APP_ENV": "production",
"LOG_LEVEL": "warn",
"APP_NAME": "Derbent",
"BASE_URL": "https://derbent.example.com",
"PBKDF2_ITERATIONS": "100000",
"AUDIT_LOG_RETENTION_DAYS": "30",
"COOKIE_DOMAIN": ".example.com",
"RESEND_DOMAIN": "destek.example.com",
},
Simple Integration Middleware
Integrating a new app takes minutes. You simply add a Service Binding and a few lines of middleware to check the session:
export async function verifyWithDerbent(c: Context, appId: string) {
const cache = caches.default;
const cookie = c.req.header("Cookie") || "";
// If the cookie is empty, we can skip the fetch entirely to save CPU
if (!cookie) {
return c.json({ error: "Unauthorized" }, 401);
}
const clientIp = c.req.header("cf-connecting-ip") || "127.0.0.1";
const clientUa = c.req.header("user-agent") || "unknown";
// SECURITY: Cloudflare Cache API ignores the 'Vary: Cookie' header.
// To prevent cross-session leaking, we create a unique cache key by hashing the cookie.
const encoder = new TextEncoder();
const data = encoder.encode(cookie);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const cookieHash = hashArray
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
// PERFORMANCE: Use the consuming Worker's actual hostname to prevent DNS lookup penalties in the Cache API.
const currentUrl = new URL(c.req.url);
const cacheUrl = new URL(`${currentUrl.origin}/_internal_auth_cache`);
cacheUrl.searchParams.set("app_id", appId);
cacheUrl.searchParams.set("cookie_hash", cookieHash);
const cacheKey = new Request(cacheUrl.toString());
let response = await cache.match(cacheKey);
if (!response) {
// The actual request to Derbent via Service Binding.
const fetchReq = new Request(
`https://auth.internal/internal/verify?app_id=${appId}`,
{
headers: {
Cookie: cookie,
"Derbent-Client-IP": clientIp,
"Derbent-Client-UA": clientUa,
},
},
);
response = await c.env.DERBENT_SERVICE.fetch(fetchReq);
if (response.ok) {
// Cache the response against our unique cacheKey
await cache.put(cacheKey, response.clone());
}
}
return response;
}
The Roadmap: Native Edge & Admin Control
Derbent is an evolving project. Here is what is coming next:
1. Moving to Cloudflare Native Email
Currently, Derbent uses Cloudflare Queues to pass email tasks to the Resend API. However, Cloudflare is set to release a native Workers Email sending service. As soon as it’s available, I will migrate to it to keep the entire stack within the Cloudflare ecosystem, reducing external dependencies and latency.
2. Admin Dashboard
I am planning a dedicated Admin Dashboard for Derbent. This will be a separate interface to:
- Manage Users: Block, delete, or update user information.
- Audit Logs: A searchable UI for the
audit_logstable in D1 to monitor security events. - Application Whitelisting: Manage which apps are allowed to authenticate via the SSO engine.
Conclusion
By centralizing authentication on the Edge, I’ve managed to increase the security of my applications while significantly reducing development time for new projects. Derbent proves that with D1, KV, and Service Bindings, you can build a robust, enterprise-grade auth system without the enterprise price tag.