Repository files navigation

Derbent Auth Engine

Self-hosted authentication for Cloudflare Workers.

  • Cross-subdomain SSO
  • OAuth login (GitHub)
  • Session-based auth (no JWTs)
  • Session hijack protection
  • Built using D1 + KV
  • Audit logging
User
│
▼
App Worker ── Service Binding ──► Derbent Auth Worker
│ │
│ ├── KV (sessions)
│ └── D1 (users + audit logs)
▼
Cloudflare Cache

Getting Started

You can deploy Derbent using the automated 1-click deploy button or manually via the CLI.

Option 1: 1-Click Deploy (Recommended)

Deploy to Cloudflare

The button above will automatically clone this repository to your GitHub account, provision your Cloudflare resources (KV, D1, Queues), run the required database migrations, and safely prompt you for the necessary environment variables.

Once deployed, clone your new repository locally and proceed to Step 3: Registering Your Apps below to configure your allowed applications.


Option 2: Manual Setup

1. Setup

git clone https://github.com/medreseli/derbent.git
cd derbent
npm install

2. Infrastructure Setup

You need to create your own Cloudflare resources for this instance:

  1. KV:npx wrangler kv namespace create KV
  2. D1:npx wrangler d1 create db-derbent
  3. Queue:npx wrangler queues create derbent-email-queue
  4. Paste the generated IDs into your wrangler.jsonc.

3. Registering Your Apps

Derbent uses a strict whitelist to determine which apps are allowed to authenticate. Open src/config/apps.ts and add your applications (e.g., geveze, namedar) to the ALLOWED_APPS array and REGISTERED_APPS object along with their production and development URLs.

4. Environment Configuration

Create a .dev.vars file for development. For production, use wrangler secret.

# APP_ENV: 'development' or 'production'APP_ENV=developmentLOG_LEVEL=debugAPP_NAME=Derbent AuthCOOKIE_DOMAIN=localhostBASE_URL=http://localhost:7777RESEND_API_KEY=re_your_api_key_hereRESEND_DOMAIN=your-verified-domain.comGITHUB_CLIENT_ID=your_github_client_idGITHUB_CLIENT_SECRET=your_github_client_secret

Note for GitHub Login: You should create 2 OAuth apps in GitHub. One for local testing and the other for production. The Authorization callback URL format is https://<your-domain>/auth/github/callback. When you deploy your app, do not forget to use the production OAuth app's client ID and secret.

5. Database

Prepare database:

npx wrangler d1 migrations apply db-derbent --local

6. Run

Run locally:

npm run dev

Integration for Consuming Apps

Derbent acts as a sidecar for your other services. Use Service Bindings to connect them without touching the public internet.

1. Wrangler Configuration

In your consuming app's wrangler.jsonc, add the service binding:

{
"services": [
{
"binding": "DERBENT_SERVICE",
"service": "derbent", // Name of the Derbent Worker
},
],
}

2. Verification Middleware

Every consuming app (e.g., geveze, namedar) should use the following pattern to verify users. Important: You must pass the end-user's IP and User-Agent using the Derbent-Client-* headers to maintain audit logging and session hijack protection.

exportasyncfunctionverifyWithDerbent(c: Context,appId: string){constcache=caches.default;constcookie=c.req.header('Cookie')||'';// If the cookie is empty, we can skip the fetch entirely to save CPUif(!cookie){returnc.json({error: 'Unauthorized'},401);}constclientIp=c.req.header('cf-connecting-ip')||'127.0.0.1';constclientUa=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.constencoder=newTextEncoder();constdata=encoder.encode(cookie);consthashBuffer=awaitcrypto.subtle.digest('SHA-256',data);consthashArray=Array.from(newUint8Array(hashBuffer));constcookieHash=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.constcurrentUrl=newURL(c.req.url);constcacheUrl=newURL(`${currentUrl.origin}/_internal_auth_cache`);cacheUrl.searchParams.set('app_id',appId);cacheUrl.searchParams.set('cookie_hash',cookieHash);constcacheKey=newRequest(cacheUrl.toString());letresponse=awaitcache.match(cacheKey);if(!response){constisDev=c.env.APP_ENV==='development';// In production, we use Service Bindings via an internal-only URL.// In development, we use global fetch to communicate with the local Derbent port.constauthUrl=isDev
? `http://localhost:7777/internal/verify?app_id=${appId}`
: `https://auth.internal/internal/verify?app_id=${appId}`;constfetchReq=newRequest(authUrl,{headers: {Cookie: cookie,'Derbent-Client-IP': clientIp,'Derbent-Client-UA': clientUa,},});response=isDev
? awaitfetch(fetchReq)
: awaitc.env.DERBENT_SERVICE.fetch(fetchReq);if(response.ok){// Cache the response against our unique cacheKeyawaitcache.put(cacheKey,response.clone());}}returnresponse;}

Note: Derbent sends Vary: Cookie and Cache-Control: private, max-age=60 by default.

3. Internal API Reference

Consuming apps communicate with Derbent internally via Service Bindings.

GET /internal/verify

Verifies the session cookie and returns the user's session data.

Request:

  • Query:?app_id=your_app_id (e.g., hodan, sso)
  • Headers:
    • Cookie: The raw cookie string from the user's request.
    • Derbent-Client-IP: The user's IP (for hijack protection).
    • Derbent-Client-UA: The user's User-Agent (for hijack protection).

Response (200 OK):

{
"userId": "018f3a5b-7b2a-7c81-9d4f-123456789abc",
"email": "user@example.com",
"appId": "hodan",
"createdAt": 1709654321000,
"ip": "203.0.113.42",
"userAgent": "Mozilla/5.0...",
"tokenVersion": 1,
"data": {} // Custom app-specific metadata
}

Errors: Returns 401 Unauthorized or 403 Forbidden if the session is invalid, expired, or tied to a different IP/UA context.

POST /internal/logout

Destroys the current active session.

Request:

  • Query:?app_id=your_app_id
  • Headers: Same as /internal/verify

Response (200 OK):

{
"success": true
}

Admin Dashboard API

Derbent exposes privileged endpoints to manage users and system security. These endpoints must be accessed with an Authorization: Bearer <DERBENT_API_KEY> header.

Users Management

EndpointMethodDescription
/admin/usersGETList users (?page=1&limit=20&search=email@)
/admin/users/:idGETGet a single user's detailed profile
/admin/users/:idPATCHUpdate user metadata, app, or email status
/admin/users/:id/passwordPOSTForce reset a user's password
/admin/users/:id/2faDELETEDisable 2FA
/admin/users/:idDELETEPermanently delete a user & audit logs

Admin Dashboard Secret

Generate admin dashboard secret with this command:

openssl rand -hex 32

For Local Development

DERBENT_API_KEY=your_generated_hex_string_here

For Production

npx wrangler secret put DERBENT_API_KEY

Testing

npm run test

Architecture & Decisions

  • No JWTs: Opaque tokens only.
  • Stateful: Session data stored in Cloudflare KV.
  • Root Domain Cookies: Scoped to .yourdomain.com for cross-subdomain SSO.
  • Isolation: Supports both Global sso accounts and app-specific accounts.

Security & Business Logic

  1. SSO Priority: Once an sso account exists for an email, app-specific accounts for that email cannot be created.
  2. Rate Limiting: Protects against brute force.
  3. Audit Logging: Every action is recorded in the D1 audit_logs table.
  4. Hijack Prevention: Sessions are bound to User-Agent and IP.

Use Cases

Derbent works well for:

• SaaS apps on Cloudflare Workers • Multi-subdomain applications • Edge-native APIs • Self-hosted authentication systems • Replacing Auth0 for Workers projects

Assets


About

Self-hosted authentication for Cloudflare Workers. Session-based auth with KV + D1, cross-subdomain SSO, GitHub OAuth, and audit logging — without JWT complexity.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Derbent Auth Engine

Self-hosted authentication for Cloudflare Workers.

  • Cross-subdomain SSO
  • OAuth login (GitHub)
  • Session-based auth (no JWTs)
  • Session hijack protection
  • Built using D1 + KV
  • Audit logging
User
│
▼
App Worker ── Service Binding ──► Derbent Auth Worker
│ │
│ ├── KV (sessions)
│ └── D1 (users + audit logs)
▼
Cloudflare Cache

Getting Started

You can deploy Derbent using the automated 1-click deploy button or manually via the CLI.

Option 1: 1-Click Deploy (Recommended)

Deploy to Cloudflare

The button above will automatically clone this repository to your GitHub account, provision your Cloudflare resources (KV, D1, Queues), run the required database migrations, and safely prompt you for the necessary environment variables.

Once deployed, clone your new repository locally and proceed to Step 3: Registering Your Apps below to configure your allowed applications.


Option 2: Manual Setup

1. Setup

git clone https://github.com/medreseli/derbent.git
cd derbent
npm install

2. Infrastructure Setup

You need to create your own Cloudflare resources for this instance:

  1. KV:npx wrangler kv namespace create KV
  2. D1:npx wrangler d1 create db-derbent
  3. Queue:npx wrangler queues create derbent-email-queue
  4. Paste the generated IDs into your wrangler.jsonc.

3. Registering Your Apps

Derbent uses a strict whitelist to determine which apps are allowed to authenticate. Open src/config/apps.ts and add your applications (e.g., geveze, namedar) to the ALLOWED_APPS array and REGISTERED_APPS object along with their production and development URLs.

4. Environment Configuration

Create a .dev.vars file for development. For production, use wrangler secret.

# APP_ENV: 'development' or 'production'APP_ENV=developmentLOG_LEVEL=debugAPP_NAME=Derbent AuthCOOKIE_DOMAIN=localhostBASE_URL=http://localhost:7777RESEND_API_KEY=re_your_api_key_hereRESEND_DOMAIN=your-verified-domain.comGITHUB_CLIENT_ID=your_github_client_idGITHUB_CLIENT_SECRET=your_github_client_secret

Note for GitHub Login: You should create 2 OAuth apps in GitHub. One for local testing and the other for production. The Authorization callback URL format is https://<your-domain>/auth/github/callback. When you deploy your app, do not forget to use the production OAuth app's client ID and secret.

5. Database

Prepare database:

npx wrangler d1 migrations apply db-derbent --local

6. Run

Run locally:

npm run dev

Integration for Consuming Apps

Derbent acts as a sidecar for your other services. Use Service Bindings to connect them without touching the public internet.

1. Wrangler Configuration

In your consuming app's wrangler.jsonc, add the service binding:

{
"services": [
{
"binding": "DERBENT_SERVICE",
"service": "derbent", // Name of the Derbent Worker
},
],
}

2. Verification Middleware

Every consuming app (e.g., geveze, namedar) should use the following pattern to verify users. Important: You must pass the end-user's IP and User-Agent using the Derbent-Client-* headers to maintain audit logging and session hijack protection.

exportasyncfunctionverifyWithDerbent(c: Context,appId: string){constcache=caches.default;constcookie=c.req.header('Cookie')||'';// If the cookie is empty, we can skip the fetch entirely to save CPUif(!cookie){returnc.json({error: 'Unauthorized'},401);}constclientIp=c.req.header('cf-connecting-ip')||'127.0.0.1';constclientUa=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.constencoder=newTextEncoder();constdata=encoder.encode(cookie);consthashBuffer=awaitcrypto.subtle.digest('SHA-256',data);consthashArray=Array.from(newUint8Array(hashBuffer));constcookieHash=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.constcurrentUrl=newURL(c.req.url);constcacheUrl=newURL(`${currentUrl.origin}/_internal_auth_cache`);cacheUrl.searchParams.set('app_id',appId);cacheUrl.searchParams.set('cookie_hash',cookieHash);constcacheKey=newRequest(cacheUrl.toString());letresponse=awaitcache.match(cacheKey);if(!response){constisDev=c.env.APP_ENV==='development';// In production, we use Service Bindings via an internal-only URL.// In development, we use global fetch to communicate with the local Derbent port.constauthUrl=isDev
? `http://localhost:7777/internal/verify?app_id=${appId}`
: `https://auth.internal/internal/verify?app_id=${appId}`;constfetchReq=newRequest(authUrl,{headers: {Cookie: cookie,'Derbent-Client-IP': clientIp,'Derbent-Client-UA': clientUa,},});response=isDev
? awaitfetch(fetchReq)
: awaitc.env.DERBENT_SERVICE.fetch(fetchReq);if(response.ok){// Cache the response against our unique cacheKeyawaitcache.put(cacheKey,response.clone());}}returnresponse;}

Note: Derbent sends Vary: Cookie and Cache-Control: private, max-age=60 by default.

3. Internal API Reference

Consuming apps communicate with Derbent internally via Service Bindings.

GET /internal/verify

Verifies the session cookie and returns the user's session data.

Request:

  • Query:?app_id=your_app_id (e.g., hodan, sso)
  • Headers:
    • Cookie: The raw cookie string from the user's request.
    • Derbent-Client-IP: The user's IP (for hijack protection).
    • Derbent-Client-UA: The user's User-Agent (for hijack protection).

Response (200 OK):

{
"userId": "018f3a5b-7b2a-7c81-9d4f-123456789abc",
"email": "user@example.com",
"appId": "hodan",
"createdAt": 1709654321000,
"ip": "203.0.113.42",
"userAgent": "Mozilla/5.0...",
"tokenVersion": 1,
"data": {} // Custom app-specific metadata
}

Errors: Returns 401 Unauthorized or 403 Forbidden if the session is invalid, expired, or tied to a different IP/UA context.

POST /internal/logout

Destroys the current active session.

Request:

  • Query:?app_id=your_app_id
  • Headers: Same as /internal/verify

Response (200 OK):

{
"success": true
}

Admin Dashboard API

Derbent exposes privileged endpoints to manage users and system security. These endpoints must be accessed with an Authorization: Bearer <DERBENT_API_KEY> header.

Users Management

EndpointMethodDescription
/admin/usersGETList users (?page=1&limit=20&search=email@)
/admin/users/:idGETGet a single user's detailed profile
/admin/users/:idPATCHUpdate user metadata, app, or email status
/admin/users/:id/passwordPOSTForce reset a user's password
/admin/users/:id/2faDELETEDisable 2FA
/admin/users/:idDELETEPermanently delete a user & audit logs

Admin Dashboard Secret

Generate admin dashboard secret with this command:

openssl rand -hex 32

For Local Development

DERBENT_API_KEY=your_generated_hex_string_here

For Production

npx wrangler secret put DERBENT_API_KEY

Testing

npm run test

Architecture & Decisions

  • No JWTs: Opaque tokens only.
  • Stateful: Session data stored in Cloudflare KV.
  • Root Domain Cookies: Scoped to .yourdomain.com for cross-subdomain SSO.
  • Isolation: Supports both Global sso accounts and app-specific accounts.

Security & Business Logic

  1. SSO Priority: Once an sso account exists for an email, app-specific accounts for that email cannot be created.
  2. Rate Limiting: Protects against brute force.
  3. Audit Logging: Every action is recorded in the D1 audit_logs table.
  4. Hijack Prevention: Sessions are bound to User-Agent and IP.

Use Cases

Derbent works well for:

• SaaS apps on Cloudflare Workers • Multi-subdomain applications • Edge-native APIs • Self-hosted authentication systems • Replacing Auth0 for Workers projects

Assets


About

Self-hosted authentication for Cloudflare Workers. Session-based auth with KV + D1, cross-subdomain SSO, GitHub OAuth, and audit logging — without JWT complexity.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Derbent Auth Engine

Self-hosted authentication for Cloudflare Workers.

  • Cross-subdomain SSO
  • OAuth login (GitHub)
  • Session-based auth (no JWTs)
  • Session hijack protection
  • Built using D1 + KV
  • Audit logging
User
│
▼
App Worker ── Service Binding ──► Derbent Auth Worker
│ │
│ ├── KV (sessions)
│ └── D1 (users + audit logs)
▼
Cloudflare Cache

Getting Started

You can deploy Derbent using the automated 1-click deploy button or manually via the CLI.

Option 1: 1-Click Deploy (Recommended)

Deploy to Cloudflare

The button above will automatically clone this repository to your GitHub account, provision your Cloudflare resources (KV, D1, Queues), run the required database migrations, and safely prompt you for the necessary environment variables.

Once deployed, clone your new repository locally and proceed to Step 3: Registering Your Apps below to configure your allowed applications.


Option 2: Manual Setup

1. Setup

git clone https://github.com/medreseli/derbent.git
cd derbent
npm install

2. Infrastructure Setup

You need to create your own Cloudflare resources for this instance:

  1. KV:npx wrangler kv namespace create KV
  2. D1:npx wrangler d1 create db-derbent
  3. Queue:npx wrangler queues create derbent-email-queue
  4. Paste the generated IDs into your wrangler.jsonc.

3. Registering Your Apps

Derbent uses a strict whitelist to determine which apps are allowed to authenticate. Open src/config/apps.ts and add your applications (e.g., geveze, namedar) to the ALLOWED_APPS array and REGISTERED_APPS object along with their production and development URLs.

4. Environment Configuration

Create a .dev.vars file for development. For production, use wrangler secret.

# APP_ENV: 'development' or 'production'APP_ENV=developmentLOG_LEVEL=debugAPP_NAME=Derbent AuthCOOKIE_DOMAIN=localhostBASE_URL=http://localhost:7777RESEND_API_KEY=re_your_api_key_hereRESEND_DOMAIN=your-verified-domain.comGITHUB_CLIENT_ID=your_github_client_idGITHUB_CLIENT_SECRET=your_github_client_secret

Note for GitHub Login: You should create 2 OAuth apps in GitHub. One for local testing and the other for production. The Authorization callback URL format is https://<your-domain>/auth/github/callback. When you deploy your app, do not forget to use the production OAuth app's client ID and secret.

5. Database

Prepare database:

npx wrangler d1 migrations apply db-derbent --local

6. Run

Run locally:

npm run dev

Integration for Consuming Apps

Derbent acts as a sidecar for your other services. Use Service Bindings to connect them without touching the public internet.

1. Wrangler Configuration

In your consuming app's wrangler.jsonc, add the service binding:

{
"services": [
{
"binding": "DERBENT_SERVICE",
"service": "derbent", // Name of the Derbent Worker
},
],
}

2. Verification Middleware

Every consuming app (e.g., geveze, namedar) should use the following pattern to verify users. Important: You must pass the end-user's IP and User-Agent using the Derbent-Client-* headers to maintain audit logging and session hijack protection.

exportasyncfunctionverifyWithDerbent(c: Context,appId: string){constcache=caches.default;constcookie=c.req.header('Cookie')||'';// If the cookie is empty, we can skip the fetch entirely to save CPUif(!cookie){returnc.json({error: 'Unauthorized'},401);}constclientIp=c.req.header('cf-connecting-ip')||'127.0.0.1';constclientUa=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.constencoder=newTextEncoder();constdata=encoder.encode(cookie);consthashBuffer=awaitcrypto.subtle.digest('SHA-256',data);consthashArray=Array.from(newUint8Array(hashBuffer));constcookieHash=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.constcurrentUrl=newURL(c.req.url);constcacheUrl=newURL(`${currentUrl.origin}/_internal_auth_cache`);cacheUrl.searchParams.set('app_id',appId);cacheUrl.searchParams.set('cookie_hash',cookieHash);constcacheKey=newRequest(cacheUrl.toString());letresponse=awaitcache.match(cacheKey);if(!response){constisDev=c.env.APP_ENV==='development';// In production, we use Service Bindings via an internal-only URL.// In development, we use global fetch to communicate with the local Derbent port.constauthUrl=isDev
? `http://localhost:7777/internal/verify?app_id=${appId}`
: `https://auth.internal/internal/verify?app_id=${appId}`;constfetchReq=newRequest(authUrl,{headers: {Cookie: cookie,'Derbent-Client-IP': clientIp,'Derbent-Client-UA': clientUa,},});response=isDev
? awaitfetch(fetchReq)
: awaitc.env.DERBENT_SERVICE.fetch(fetchReq);if(response.ok){// Cache the response against our unique cacheKeyawaitcache.put(cacheKey,response.clone());}}returnresponse;}

Note: Derbent sends Vary: Cookie and Cache-Control: private, max-age=60 by default.

3. Internal API Reference

Consuming apps communicate with Derbent internally via Service Bindings.

GET /internal/verify

Verifies the session cookie and returns the user's session data.

Request:

  • Query:?app_id=your_app_id (e.g., hodan, sso)
  • Headers:
    • Cookie: The raw cookie string from the user's request.
    • Derbent-Client-IP: The user's IP (for hijack protection).
    • Derbent-Client-UA: The user's User-Agent (for hijack protection).

Response (200 OK):

{
"userId": "018f3a5b-7b2a-7c81-9d4f-123456789abc",
"email": "user@example.com",
"appId": "hodan",
"createdAt": 1709654321000,
"ip": "203.0.113.42",
"userAgent": "Mozilla/5.0...",
"tokenVersion": 1,
"data": {} // Custom app-specific metadata
}

Errors: Returns 401 Unauthorized or 403 Forbidden if the session is invalid, expired, or tied to a different IP/UA context.

POST /internal/logout

Destroys the current active session.

Request:

  • Query:?app_id=your_app_id
  • Headers: Same as /internal/verify

Response (200 OK):

{
"success": true
}

Admin Dashboard API

Derbent exposes privileged endpoints to manage users and system security. These endpoints must be accessed with an Authorization: Bearer <DERBENT_API_KEY> header.

Users Management

EndpointMethodDescription
/admin/usersGETList users (?page=1&limit=20&search=email@)
/admin/users/:idGETGet a single user's detailed profile
/admin/users/:idPATCHUpdate user metadata, app, or email status
/admin/users/:id/passwordPOSTForce reset a user's password
/admin/users/:id/2faDELETEDisable 2FA
/admin/users/:idDELETEPermanently delete a user & audit logs

Admin Dashboard Secret

Generate admin dashboard secret with this command:

openssl rand -hex 32

For Local Development

DERBENT_API_KEY=your_generated_hex_string_here

For Production

npx wrangler secret put DERBENT_API_KEY

Testing

npm run test

Architecture & Decisions

  • No JWTs: Opaque tokens only.
  • Stateful: Session data stored in Cloudflare KV.
  • Root Domain Cookies: Scoped to .yourdomain.com for cross-subdomain SSO.
  • Isolation: Supports both Global sso accounts and app-specific accounts.

Security & Business Logic

  1. SSO Priority: Once an sso account exists for an email, app-specific accounts for that email cannot be created.
  2. Rate Limiting: Protects against brute force.
  3. Audit Logging: Every action is recorded in the D1 audit_logs table.
  4. Hijack Prevention: Sessions are bound to User-Agent and IP.

Use Cases

Derbent works well for:

• SaaS apps on Cloudflare Workers • Multi-subdomain applications • Edge-native APIs • Self-hosted authentication systems • Replacing Auth0 for Workers projects

Assets


About

Self-hosted authentication for Cloudflare Workers. Session-based auth with KV + D1, cross-subdomain SSO, GitHub OAuth, and audit logging — without JWT complexity.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Derbent Auth Engine

Self-hosted authentication for Cloudflare Workers.

  • Cross-subdomain SSO
  • OAuth login (GitHub)
  • Session-based auth (no JWTs)
  • Session hijack protection
  • Built using D1 + KV
  • Audit logging
User
│
▼
App Worker ── Service Binding ──► Derbent Auth Worker
│ │
│ ├── KV (sessions)
│ └── D1 (users + audit logs)
▼
Cloudflare Cache

Getting Started

You can deploy Derbent using the automated 1-click deploy button or manually via the CLI.

Option 1: 1-Click Deploy (Recommended)

Deploy to Cloudflare

The button above will automatically clone this repository to your GitHub account, provision your Cloudflare resources (KV, D1, Queues), run the required database migrations, and safely prompt you for the necessary environment variables.

Once deployed, clone your new repository locally and proceed to Step 3: Registering Your Apps below to configure your allowed applications.


Option 2: Manual Setup

1. Setup

git clone https://github.com/medreseli/derbent.git
cd derbent
npm install

2. Infrastructure Setup

You need to create your own Cloudflare resources for this instance:

  1. KV:npx wrangler kv namespace create KV
  2. D1:npx wrangler d1 create db-derbent
  3. Queue:npx wrangler queues create derbent-email-queue
  4. Paste the generated IDs into your wrangler.jsonc.

3. Registering Your Apps

Derbent uses a strict whitelist to determine which apps are allowed to authenticate. Open src/config/apps.ts and add your applications (e.g., geveze, namedar) to the ALLOWED_APPS array and REGISTERED_APPS object along with their production and development URLs.

4. Environment Configuration

Create a .dev.vars file for development. For production, use wrangler secret.

# APP_ENV: 'development' or 'production'APP_ENV=developmentLOG_LEVEL=debugAPP_NAME=Derbent AuthCOOKIE_DOMAIN=localhostBASE_URL=http://localhost:7777RESEND_API_KEY=re_your_api_key_hereRESEND_DOMAIN=your-verified-domain.comGITHUB_CLIENT_ID=your_github_client_idGITHUB_CLIENT_SECRET=your_github_client_secret

Note for GitHub Login: You should create 2 OAuth apps in GitHub. One for local testing and the other for production. The Authorization callback URL format is https://<your-domain>/auth/github/callback. When you deploy your app, do not forget to use the production OAuth app's client ID and secret.

5. Database

Prepare database:

npx wrangler d1 migrations apply db-derbent --local

6. Run

Run locally:

npm run dev

Integration for Consuming Apps

Derbent acts as a sidecar for your other services. Use Service Bindings to connect them without touching the public internet.

1. Wrangler Configuration

In your consuming app's wrangler.jsonc, add the service binding:

{
"services": [
{
"binding": "DERBENT_SERVICE",
"service": "derbent", // Name of the Derbent Worker
},
],
}

2. Verification Middleware

Every consuming app (e.g., geveze, namedar) should use the following pattern to verify users. Important: You must pass the end-user's IP and User-Agent using the Derbent-Client-* headers to maintain audit logging and session hijack protection.

exportasyncfunctionverifyWithDerbent(c: Context,appId: string){constcache=caches.default;constcookie=c.req.header('Cookie')||'';// If the cookie is empty, we can skip the fetch entirely to save CPUif(!cookie){returnc.json({error: 'Unauthorized'},401);}constclientIp=c.req.header('cf-connecting-ip')||'127.0.0.1';constclientUa=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.constencoder=newTextEncoder();constdata=encoder.encode(cookie);consthashBuffer=awaitcrypto.subtle.digest('SHA-256',data);consthashArray=Array.from(newUint8Array(hashBuffer));constcookieHash=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.constcurrentUrl=newURL(c.req.url);constcacheUrl=newURL(`${currentUrl.origin}/_internal_auth_cache`);cacheUrl.searchParams.set('app_id',appId);cacheUrl.searchParams.set('cookie_hash',cookieHash);constcacheKey=newRequest(cacheUrl.toString());letresponse=awaitcache.match(cacheKey);if(!response){constisDev=c.env.APP_ENV==='development';// In production, we use Service Bindings via an internal-only URL.// In development, we use global fetch to communicate with the local Derbent port.constauthUrl=isDev
? `http://localhost:7777/internal/verify?app_id=${appId}`
: `https://auth.internal/internal/verify?app_id=${appId}`;constfetchReq=newRequest(authUrl,{headers: {Cookie: cookie,'Derbent-Client-IP': clientIp,'Derbent-Client-UA': clientUa,},});response=isDev
? awaitfetch(fetchReq)
: awaitc.env.DERBENT_SERVICE.fetch(fetchReq);if(response.ok){// Cache the response against our unique cacheKeyawaitcache.put(cacheKey,response.clone());}}returnresponse;}

Note: Derbent sends Vary: Cookie and Cache-Control: private, max-age=60 by default.

3. Internal API Reference

Consuming apps communicate with Derbent internally via Service Bindings.

GET /internal/verify

Verifies the session cookie and returns the user's session data.

Request:

  • Query:?app_id=your_app_id (e.g., hodan, sso)
  • Headers:
    • Cookie: The raw cookie string from the user's request.
    • Derbent-Client-IP: The user's IP (for hijack protection).
    • Derbent-Client-UA: The user's User-Agent (for hijack protection).

Response (200 OK):

{
"userId": "018f3a5b-7b2a-7c81-9d4f-123456789abc",
"email": "user@example.com",
"appId": "hodan",
"createdAt": 1709654321000,
"ip": "203.0.113.42",
"userAgent": "Mozilla/5.0...",
"tokenVersion": 1,
"data": {} // Custom app-specific metadata
}

Errors: Returns 401 Unauthorized or 403 Forbidden if the session is invalid, expired, or tied to a different IP/UA context.

POST /internal/logout

Destroys the current active session.

Request:

  • Query:?app_id=your_app_id
  • Headers: Same as /internal/verify

Response (200 OK):

{
"success": true
}

Admin Dashboard API

Derbent exposes privileged endpoints to manage users and system security. These endpoints must be accessed with an Authorization: Bearer <DERBENT_API_KEY> header.

Users Management

EndpointMethodDescription
/admin/usersGETList users (?page=1&limit=20&search=email@)
/admin/users/:idGETGet a single user's detailed profile
/admin/users/:idPATCHUpdate user metadata, app, or email status
/admin/users/:id/passwordPOSTForce reset a user's password
/admin/users/:id/2faDELETEDisable 2FA
/admin/users/:idDELETEPermanently delete a user & audit logs

Admin Dashboard Secret

Generate admin dashboard secret with this command:

openssl rand -hex 32

For Local Development

DERBENT_API_KEY=your_generated_hex_string_here

For Production

npx wrangler secret put DERBENT_API_KEY

Testing

npm run test

Architecture & Decisions

  • No JWTs: Opaque tokens only.
  • Stateful: Session data stored in Cloudflare KV.
  • Root Domain Cookies: Scoped to .yourdomain.com for cross-subdomain SSO.
  • Isolation: Supports both Global sso accounts and app-specific accounts.

Security & Business Logic

  1. SSO Priority: Once an sso account exists for an email, app-specific accounts for that email cannot be created.
  2. Rate Limiting: Protects against brute force.
  3. Audit Logging: Every action is recorded in the D1 audit_logs table.
  4. Hijack Prevention: Sessions are bound to User-Agent and IP.

Use Cases

Derbent works well for:

• SaaS apps on Cloudflare Workers • Multi-subdomain applications • Edge-native APIs • Self-hosted authentication systems • Replacing Auth0 for Workers projects

Assets


About

Self-hosted authentication for Cloudflare Workers. Session-based auth with KV + D1, cross-subdomain SSO, GitHub OAuth, and audit logging — without JWT complexity.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Derbent Auth Engine

Self-hosted authentication for Cloudflare Workers.

  • Cross-subdomain SSO
  • OAuth login (GitHub)
  • Session-based auth (no JWTs)
  • Session hijack protection
  • Built using D1 + KV
  • Audit logging
User
│
▼
App Worker ── Service Binding ──► Derbent Auth Worker
│ │
│ ├── KV (sessions)
│ └── D1 (users + audit logs)
▼
Cloudflare Cache

Getting Started

You can deploy Derbent using the automated 1-click deploy button or manually via the CLI.

Option 1: 1-Click Deploy (Recommended)

Deploy to Cloudflare

The button above will automatically clone this repository to your GitHub account, provision your Cloudflare resources (KV, D1, Queues), run the required database migrations, and safely prompt you for the necessary environment variables.

Once deployed, clone your new repository locally and proceed to Step 3: Registering Your Apps below to configure your allowed applications.


Option 2: Manual Setup

1. Setup

git clone https://github.com/medreseli/derbent.git
cd derbent
npm install

2. Infrastructure Setup

You need to create your own Cloudflare resources for this instance:

  1. KV:npx wrangler kv namespace create KV
  2. D1:npx wrangler d1 create db-derbent
  3. Queue:npx wrangler queues create derbent-email-queue
  4. Paste the generated IDs into your wrangler.jsonc.

3. Registering Your Apps

Derbent uses a strict whitelist to determine which apps are allowed to authenticate. Open src/config/apps.ts and add your applications (e.g., geveze, namedar) to the ALLOWED_APPS array and REGISTERED_APPS object along with their production and development URLs.

4. Environment Configuration

Create a .dev.vars file for development. For production, use wrangler secret.

# APP_ENV: 'development' or 'production'APP_ENV=developmentLOG_LEVEL=debugAPP_NAME=Derbent AuthCOOKIE_DOMAIN=localhostBASE_URL=http://localhost:7777RESEND_API_KEY=re_your_api_key_hereRESEND_DOMAIN=your-verified-domain.comGITHUB_CLIENT_ID=your_github_client_idGITHUB_CLIENT_SECRET=your_github_client_secret

Note for GitHub Login: You should create 2 OAuth apps in GitHub. One for local testing and the other for production. The Authorization callback URL format is https://<your-domain>/auth/github/callback. When you deploy your app, do not forget to use the production OAuth app's client ID and secret.

5. Database

Prepare database:

npx wrangler d1 migrations apply db-derbent --local

6. Run

Run locally:

npm run dev

Integration for Consuming Apps

Derbent acts as a sidecar for your other services. Use Service Bindings to connect them without touching the public internet.

1. Wrangler Configuration

In your consuming app's wrangler.jsonc, add the service binding:

{
"services": [
{
"binding": "DERBENT_SERVICE",
"service": "derbent", // Name of the Derbent Worker
},
],
}

2. Verification Middleware

Every consuming app (e.g., geveze, namedar) should use the following pattern to verify users. Important: You must pass the end-user's IP and User-Agent using the Derbent-Client-* headers to maintain audit logging and session hijack protection.

exportasyncfunctionverifyWithDerbent(c: Context,appId: string){constcache=caches.default;constcookie=c.req.header('Cookie')||'';// If the cookie is empty, we can skip the fetch entirely to save CPUif(!cookie){returnc.json({error: 'Unauthorized'},401);}constclientIp=c.req.header('cf-connecting-ip')||'127.0.0.1';constclientUa=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.constencoder=newTextEncoder();constdata=encoder.encode(cookie);consthashBuffer=awaitcrypto.subtle.digest('SHA-256',data);consthashArray=Array.from(newUint8Array(hashBuffer));constcookieHash=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.constcurrentUrl=newURL(c.req.url);constcacheUrl=newURL(`${currentUrl.origin}/_internal_auth_cache`);cacheUrl.searchParams.set('app_id',appId);cacheUrl.searchParams.set('cookie_hash',cookieHash);constcacheKey=newRequest(cacheUrl.toString());letresponse=awaitcache.match(cacheKey);if(!response){constisDev=c.env.APP_ENV==='development';// In production, we use Service Bindings via an internal-only URL.// In development, we use global fetch to communicate with the local Derbent port.constauthUrl=isDev
? `http://localhost:7777/internal/verify?app_id=${appId}`
: `https://auth.internal/internal/verify?app_id=${appId}`;constfetchReq=newRequest(authUrl,{headers: {Cookie: cookie,'Derbent-Client-IP': clientIp,'Derbent-Client-UA': clientUa,},});response=isDev
? awaitfetch(fetchReq)
: awaitc.env.DERBENT_SERVICE.fetch(fetchReq);if(response.ok){// Cache the response against our unique cacheKeyawaitcache.put(cacheKey,response.clone());}}returnresponse;}

Note: Derbent sends Vary: Cookie and Cache-Control: private, max-age=60 by default.

3. Internal API Reference

Consuming apps communicate with Derbent internally via Service Bindings.

GET /internal/verify

Verifies the session cookie and returns the user's session data.

Request:

  • Query:?app_id=your_app_id (e.g., hodan, sso)
  • Headers:
    • Cookie: The raw cookie string from the user's request.
    • Derbent-Client-IP: The user's IP (for hijack protection).
    • Derbent-Client-UA: The user's User-Agent (for hijack protection).

Response (200 OK):

{
"userId": "018f3a5b-7b2a-7c81-9d4f-123456789abc",
"email": "user@example.com",
"appId": "hodan",
"createdAt": 1709654321000,
"ip": "203.0.113.42",
"userAgent": "Mozilla/5.0...",
"tokenVersion": 1,
"data": {} // Custom app-specific metadata
}

Errors: Returns 401 Unauthorized or 403 Forbidden if the session is invalid, expired, or tied to a different IP/UA context.

POST /internal/logout

Destroys the current active session.

Request:

  • Query:?app_id=your_app_id
  • Headers: Same as /internal/verify

Response (200 OK):

{
"success": true
}

Admin Dashboard API

Derbent exposes privileged endpoints to manage users and system security. These endpoints must be accessed with an Authorization: Bearer <DERBENT_API_KEY> header.

Users Management

EndpointMethodDescription
/admin/usersGETList users (?page=1&limit=20&search=email@)
/admin/users/:idGETGet a single user's detailed profile
/admin/users/:idPATCHUpdate user metadata, app, or email status
/admin/users/:id/passwordPOSTForce reset a user's password
/admin/users/:id/2faDELETEDisable 2FA
/admin/users/:idDELETEPermanently delete a user & audit logs

Admin Dashboard Secret

Generate admin dashboard secret with this command:

openssl rand -hex 32

For Local Development

DERBENT_API_KEY=your_generated_hex_string_here

For Production

npx wrangler secret put DERBENT_API_KEY

Testing

npm run test

Architecture & Decisions

  • No JWTs: Opaque tokens only.
  • Stateful: Session data stored in Cloudflare KV.
  • Root Domain Cookies: Scoped to .yourdomain.com for cross-subdomain SSO.
  • Isolation: Supports both Global sso accounts and app-specific accounts.

Security & Business Logic

  1. SSO Priority: Once an sso account exists for an email, app-specific accounts for that email cannot be created.
  2. Rate Limiting: Protects against brute force.
  3. Audit Logging: Every action is recorded in the D1 audit_logs table.
  4. Hijack Prevention: Sessions are bound to User-Agent and IP.

Use Cases

Derbent works well for:

• SaaS apps on Cloudflare Workers • Multi-subdomain applications • Edge-native APIs • Self-hosted authentication systems • Replacing Auth0 for Workers projects

Assets


About

Self-hosted authentication for Cloudflare Workers. Session-based auth with KV + D1, cross-subdomain SSO, GitHub OAuth, and audit logging — without JWT complexity.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Derbent Auth Engine

Self-hosted authentication for Cloudflare Workers.

  • Cross-subdomain SSO
  • OAuth login (GitHub)
  • Session-based auth (no JWTs)
  • Session hijack protection
  • Built using D1 + KV
  • Audit logging
User
│
▼
App Worker ── Service Binding ──► Derbent Auth Worker
│ │
│ ├── KV (sessions)
│ └── D1 (users + audit logs)
▼
Cloudflare Cache

Getting Started

You can deploy Derbent using the automated 1-click deploy button or manually via the CLI.

Option 1: 1-Click Deploy (Recommended)

Deploy to Cloudflare

The button above will automatically clone this repository to your GitHub account, provision your Cloudflare resources (KV, D1, Queues), run the required database migrations, and safely prompt you for the necessary environment variables.

Once deployed, clone your new repository locally and proceed to Step 3: Registering Your Apps below to configure your allowed applications.


Option 2: Manual Setup

1. Setup

git clone https://github.com/medreseli/derbent.git
cd derbent
npm install

2. Infrastructure Setup

You need to create your own Cloudflare resources for this instance:

  1. KV:npx wrangler kv namespace create KV
  2. D1:npx wrangler d1 create db-derbent
  3. Queue:npx wrangler queues create derbent-email-queue
  4. Paste the generated IDs into your wrangler.jsonc.

3. Registering Your Apps

Derbent uses a strict whitelist to determine which apps are allowed to authenticate. Open src/config/apps.ts and add your applications (e.g., geveze, namedar) to the ALLOWED_APPS array and REGISTERED_APPS object along with their production and development URLs.

4. Environment Configuration

Create a .dev.vars file for development. For production, use wrangler secret.

# APP_ENV: 'development' or 'production'APP_ENV=developmentLOG_LEVEL=debugAPP_NAME=Derbent AuthCOOKIE_DOMAIN=localhostBASE_URL=http://localhost:7777RESEND_API_KEY=re_your_api_key_hereRESEND_DOMAIN=your-verified-domain.comGITHUB_CLIENT_ID=your_github_client_idGITHUB_CLIENT_SECRET=your_github_client_secret

Note for GitHub Login: You should create 2 OAuth apps in GitHub. One for local testing and the other for production. The Authorization callback URL format is https://<your-domain>/auth/github/callback. When you deploy your app, do not forget to use the production OAuth app's client ID and secret.

5. Database

Prepare database:

npx wrangler d1 migrations apply db-derbent --local

6. Run

Run locally:

npm run dev

Integration for Consuming Apps

Derbent acts as a sidecar for your other services. Use Service Bindings to connect them without touching the public internet.

1. Wrangler Configuration

In your consuming app's wrangler.jsonc, add the service binding:

{
"services": [
{
"binding": "DERBENT_SERVICE",
"service": "derbent", // Name of the Derbent Worker
},
],
}

2. Verification Middleware

Every consuming app (e.g., geveze, namedar) should use the following pattern to verify users. Important: You must pass the end-user's IP and User-Agent using the Derbent-Client-* headers to maintain audit logging and session hijack protection.

exportasyncfunctionverifyWithDerbent(c: Context,appId: string){constcache=caches.default;constcookie=c.req.header('Cookie')||'';// If the cookie is empty, we can skip the fetch entirely to save CPUif(!cookie){returnc.json({error: 'Unauthorized'},401);}constclientIp=c.req.header('cf-connecting-ip')||'127.0.0.1';constclientUa=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.constencoder=newTextEncoder();constdata=encoder.encode(cookie);consthashBuffer=awaitcrypto.subtle.digest('SHA-256',data);consthashArray=Array.from(newUint8Array(hashBuffer));constcookieHash=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.constcurrentUrl=newURL(c.req.url);constcacheUrl=newURL(`${currentUrl.origin}/_internal_auth_cache`);cacheUrl.searchParams.set('app_id',appId);cacheUrl.searchParams.set('cookie_hash',cookieHash);constcacheKey=newRequest(cacheUrl.toString());letresponse=awaitcache.match(cacheKey);if(!response){constisDev=c.env.APP_ENV==='development';// In production, we use Service Bindings via an internal-only URL.// In development, we use global fetch to communicate with the local Derbent port.constauthUrl=isDev
? `http://localhost:7777/internal/verify?app_id=${appId}`
: `https://auth.internal/internal/verify?app_id=${appId}`;constfetchReq=newRequest(authUrl,{headers: {Cookie: cookie,'Derbent-Client-IP': clientIp,'Derbent-Client-UA': clientUa,},});response=isDev
? awaitfetch(fetchReq)
: awaitc.env.DERBENT_SERVICE.fetch(fetchReq);if(response.ok){// Cache the response against our unique cacheKeyawaitcache.put(cacheKey,response.clone());}}returnresponse;}

Note: Derbent sends Vary: Cookie and Cache-Control: private, max-age=60 by default.

3. Internal API Reference

Consuming apps communicate with Derbent internally via Service Bindings.

GET /internal/verify

Verifies the session cookie and returns the user's session data.

Request:

  • Query:?app_id=your_app_id (e.g., hodan, sso)
  • Headers:
    • Cookie: The raw cookie string from the user's request.
    • Derbent-Client-IP: The user's IP (for hijack protection).
    • Derbent-Client-UA: The user's User-Agent (for hijack protection).

Response (200 OK):

{
"userId": "018f3a5b-7b2a-7c81-9d4f-123456789abc",
"email": "user@example.com",
"appId": "hodan",
"createdAt": 1709654321000,
"ip": "203.0.113.42",
"userAgent": "Mozilla/5.0...",
"tokenVersion": 1,
"data": {} // Custom app-specific metadata
}

Errors: Returns 401 Unauthorized or 403 Forbidden if the session is invalid, expired, or tied to a different IP/UA context.

POST /internal/logout

Destroys the current active session.

Request:

  • Query:?app_id=your_app_id
  • Headers: Same as /internal/verify

Response (200 OK):

{
"success": true
}

Admin Dashboard API

Derbent exposes privileged endpoints to manage users and system security. These endpoints must be accessed with an Authorization: Bearer <DERBENT_API_KEY> header.

Users Management

EndpointMethodDescription
/admin/usersGETList users (?page=1&limit=20&search=email@)
/admin/users/:idGETGet a single user's detailed profile
/admin/users/:idPATCHUpdate user metadata, app, or email status
/admin/users/:id/passwordPOSTForce reset a user's password
/admin/users/:id/2faDELETEDisable 2FA
/admin/users/:idDELETEPermanently delete a user & audit logs

Admin Dashboard Secret

Generate admin dashboard secret with this command:

openssl rand -hex 32

For Local Development

DERBENT_API_KEY=your_generated_hex_string_here

For Production

npx wrangler secret put DERBENT_API_KEY

Testing

npm run test

Architecture & Decisions

  • No JWTs: Opaque tokens only.
  • Stateful: Session data stored in Cloudflare KV.
  • Root Domain Cookies: Scoped to .yourdomain.com for cross-subdomain SSO.
  • Isolation: Supports both Global sso accounts and app-specific accounts.

Security & Business Logic

  1. SSO Priority: Once an sso account exists for an email, app-specific accounts for that email cannot be created.
  2. Rate Limiting: Protects against brute force.
  3. Audit Logging: Every action is recorded in the D1 audit_logs table.
  4. Hijack Prevention: Sessions are bound to User-Agent and IP.

Use Cases

Derbent works well for:

• SaaS apps on Cloudflare Workers • Multi-subdomain applications • Edge-native APIs • Self-hosted authentication systems • Replacing Auth0 for Workers projects

Assets


About

Self-hosted authentication for Cloudflare Workers. Session-based auth with KV + D1, cross-subdomain SSO, GitHub OAuth, and audit logging — without JWT complexity.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Derbent Auth Engine

Self-hosted authentication for Cloudflare Workers.

  • Cross-subdomain SSO
  • OAuth login (GitHub)
  • Session-based auth (no JWTs)
  • Session hijack protection
  • Built using D1 + KV
  • Audit logging
User
│
▼
App Worker ── Service Binding ──► Derbent Auth Worker
│ │
│ ├── KV (sessions)
│ └── D1 (users + audit logs)
▼
Cloudflare Cache

Getting Started

You can deploy Derbent using the automated 1-click deploy button or manually via the CLI.

Option 1: 1-Click Deploy (Recommended)

Deploy to Cloudflare

The button above will automatically clone this repository to your GitHub account, provision your Cloudflare resources (KV, D1, Queues), run the required database migrations, and safely prompt you for the necessary environment variables.

Once deployed, clone your new repository locally and proceed to Step 3: Registering Your Apps below to configure your allowed applications.


Option 2: Manual Setup

1. Setup

git clone https://github.com/medreseli/derbent.git
cd derbent
npm install

2. Infrastructure Setup

You need to create your own Cloudflare resources for this instance:

  1. KV:npx wrangler kv namespace create KV
  2. D1:npx wrangler d1 create db-derbent
  3. Queue:npx wrangler queues create derbent-email-queue
  4. Paste the generated IDs into your wrangler.jsonc.

3. Registering Your Apps

Derbent uses a strict whitelist to determine which apps are allowed to authenticate. Open src/config/apps.ts and add your applications (e.g., geveze, namedar) to the ALLOWED_APPS array and REGISTERED_APPS object along with their production and development URLs.

4. Environment Configuration

Create a .dev.vars file for development. For production, use wrangler secret.

# APP_ENV: 'development' or 'production'APP_ENV=developmentLOG_LEVEL=debugAPP_NAME=Derbent AuthCOOKIE_DOMAIN=localhostBASE_URL=http://localhost:7777RESEND_API_KEY=re_your_api_key_hereRESEND_DOMAIN=your-verified-domain.comGITHUB_CLIENT_ID=your_github_client_idGITHUB_CLIENT_SECRET=your_github_client_secret

Note for GitHub Login: You should create 2 OAuth apps in GitHub. One for local testing and the other for production. The Authorization callback URL format is https://<your-domain>/auth/github/callback. When you deploy your app, do not forget to use the production OAuth app's client ID and secret.

5. Database

Prepare database:

npx wrangler d1 migrations apply db-derbent --local

6. Run

Run locally:

npm run dev

Integration for Consuming Apps

Derbent acts as a sidecar for your other services. Use Service Bindings to connect them without touching the public internet.

1. Wrangler Configuration

In your consuming app's wrangler.jsonc, add the service binding:

{
"services": [
{
"binding": "DERBENT_SERVICE",
"service": "derbent", // Name of the Derbent Worker
},
],
}

2. Verification Middleware

Every consuming app (e.g., geveze, namedar) should use the following pattern to verify users. Important: You must pass the end-user's IP and User-Agent using the Derbent-Client-* headers to maintain audit logging and session hijack protection.

exportasyncfunctionverifyWithDerbent(c: Context,appId: string){constcache=caches.default;constcookie=c.req.header('Cookie')||'';// If the cookie is empty, we can skip the fetch entirely to save CPUif(!cookie){returnc.json({error: 'Unauthorized'},401);}constclientIp=c.req.header('cf-connecting-ip')||'127.0.0.1';constclientUa=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.constencoder=newTextEncoder();constdata=encoder.encode(cookie);consthashBuffer=awaitcrypto.subtle.digest('SHA-256',data);consthashArray=Array.from(newUint8Array(hashBuffer));constcookieHash=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.constcurrentUrl=newURL(c.req.url);constcacheUrl=newURL(`${currentUrl.origin}/_internal_auth_cache`);cacheUrl.searchParams.set('app_id',appId);cacheUrl.searchParams.set('cookie_hash',cookieHash);constcacheKey=newRequest(cacheUrl.toString());letresponse=awaitcache.match(cacheKey);if(!response){constisDev=c.env.APP_ENV==='development';// In production, we use Service Bindings via an internal-only URL.// In development, we use global fetch to communicate with the local Derbent port.constauthUrl=isDev
? `http://localhost:7777/internal/verify?app_id=${appId}`
: `https://auth.internal/internal/verify?app_id=${appId}`;constfetchReq=newRequest(authUrl,{headers: {Cookie: cookie,'Derbent-Client-IP': clientIp,'Derbent-Client-UA': clientUa,},});response=isDev
? awaitfetch(fetchReq)
: awaitc.env.DERBENT_SERVICE.fetch(fetchReq);if(response.ok){// Cache the response against our unique cacheKeyawaitcache.put(cacheKey,response.clone());}}returnresponse;}

Note: Derbent sends Vary: Cookie and Cache-Control: private, max-age=60 by default.

3. Internal API Reference

Consuming apps communicate with Derbent internally via Service Bindings.

GET /internal/verify

Verifies the session cookie and returns the user's session data.

Request:

  • Query:?app_id=your_app_id (e.g., hodan, sso)
  • Headers:
    • Cookie: The raw cookie string from the user's request.
    • Derbent-Client-IP: The user's IP (for hijack protection).
    • Derbent-Client-UA: The user's User-Agent (for hijack protection).

Response (200 OK):

{
"userId": "018f3a5b-7b2a-7c81-9d4f-123456789abc",
"email": "user@example.com",
"appId": "hodan",
"createdAt": 1709654321000,
"ip": "203.0.113.42",
"userAgent": "Mozilla/5.0...",
"tokenVersion": 1,
"data": {} // Custom app-specific metadata
}

Errors: Returns 401 Unauthorized or 403 Forbidden if the session is invalid, expired, or tied to a different IP/UA context.

POST /internal/logout

Destroys the current active session.

Request:

  • Query:?app_id=your_app_id
  • Headers: Same as /internal/verify

Response (200 OK):

{
"success": true
}

Admin Dashboard API

Derbent exposes privileged endpoints to manage users and system security. These endpoints must be accessed with an Authorization: Bearer <DERBENT_API_KEY> header.

Users Management

EndpointMethodDescription
/admin/usersGETList users (?page=1&limit=20&search=email@)
/admin/users/:idGETGet a single user's detailed profile
/admin/users/:idPATCHUpdate user metadata, app, or email status
/admin/users/:id/passwordPOSTForce reset a user's password
/admin/users/:id/2faDELETEDisable 2FA
/admin/users/:idDELETEPermanently delete a user & audit logs

Admin Dashboard Secret

Generate admin dashboard secret with this command:

openssl rand -hex 32

For Local Development

DERBENT_API_KEY=your_generated_hex_string_here

For Production

npx wrangler secret put DERBENT_API_KEY

Testing

npm run test

Architecture & Decisions

  • No JWTs: Opaque tokens only.
  • Stateful: Session data stored in Cloudflare KV.
  • Root Domain Cookies: Scoped to .yourdomain.com for cross-subdomain SSO.
  • Isolation: Supports both Global sso accounts and app-specific accounts.

Security & Business Logic

  1. SSO Priority: Once an sso account exists for an email, app-specific accounts for that email cannot be created.
  2. Rate Limiting: Protects against brute force.
  3. Audit Logging: Every action is recorded in the D1 audit_logs table.
  4. Hijack Prevention: Sessions are bound to User-Agent and IP.

Use Cases

Derbent works well for:

• SaaS apps on Cloudflare Workers • Multi-subdomain applications • Edge-native APIs • Self-hosted authentication systems • Replacing Auth0 for Workers projects

Assets


About

Self-hosted authentication for Cloudflare Workers. Session-based auth with KV + D1, cross-subdomain SSO, GitHub OAuth, and audit logging — without JWT complexity.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Derbent Auth Engine

Self-hosted authentication for Cloudflare Workers.

  • Cross-subdomain SSO
  • OAuth login (GitHub)
  • Session-based auth (no JWTs)
  • Session hijack protection
  • Built using D1 + KV
  • Audit logging
User
│
▼
App Worker ── Service Binding ──► Derbent Auth Worker
│ │
│ ├── KV (sessions)
│ └── D1 (users + audit logs)
▼
Cloudflare Cache

Getting Started

You can deploy Derbent using the automated 1-click deploy button or manually via the CLI.

Option 1: 1-Click Deploy (Recommended)

Deploy to Cloudflare

The button above will automatically clone this repository to your GitHub account, provision your Cloudflare resources (KV, D1, Queues), run the required database migrations, and safely prompt you for the necessary environment variables.

Once deployed, clone your new repository locally and proceed to Step 3: Registering Your Apps below to configure your allowed applications.


Option 2: Manual Setup

1. Setup

git clone https://github.com/medreseli/derbent.git
cd derbent
npm install

2. Infrastructure Setup

You need to create your own Cloudflare resources for this instance:

  1. KV:npx wrangler kv namespace create KV
  2. D1:npx wrangler d1 create db-derbent
  3. Queue:npx wrangler queues create derbent-email-queue
  4. Paste the generated IDs into your wrangler.jsonc.

3. Registering Your Apps

Derbent uses a strict whitelist to determine which apps are allowed to authenticate. Open src/config/apps.ts and add your applications (e.g., geveze, namedar) to the ALLOWED_APPS array and REGISTERED_APPS object along with their production and development URLs.

4. Environment Configuration

Create a .dev.vars file for development. For production, use wrangler secret.

# APP_ENV: 'development' or 'production'APP_ENV=developmentLOG_LEVEL=debugAPP_NAME=Derbent AuthCOOKIE_DOMAIN=localhostBASE_URL=http://localhost:7777RESEND_API_KEY=re_your_api_key_hereRESEND_DOMAIN=your-verified-domain.comGITHUB_CLIENT_ID=your_github_client_idGITHUB_CLIENT_SECRET=your_github_client_secret

Note for GitHub Login: You should create 2 OAuth apps in GitHub. One for local testing and the other for production. The Authorization callback URL format is https://<your-domain>/auth/github/callback. When you deploy your app, do not forget to use the production OAuth app's client ID and secret.

5. Database

Prepare database:

npx wrangler d1 migrations apply db-derbent --local

6. Run

Run locally:

npm run dev

Integration for Consuming Apps

Derbent acts as a sidecar for your other services. Use Service Bindings to connect them without touching the public internet.

1. Wrangler Configuration

In your consuming app's wrangler.jsonc, add the service binding:

{
"services": [
{
"binding": "DERBENT_SERVICE",
"service": "derbent", // Name of the Derbent Worker
},
],
}

2. Verification Middleware

Every consuming app (e.g., geveze, namedar) should use the following pattern to verify users. Important: You must pass the end-user's IP and User-Agent using the Derbent-Client-* headers to maintain audit logging and session hijack protection.

exportasyncfunctionverifyWithDerbent(c: Context,appId: string){constcache=caches.default;constcookie=c.req.header('Cookie')||'';// If the cookie is empty, we can skip the fetch entirely to save CPUif(!cookie){returnc.json({error: 'Unauthorized'},401);}constclientIp=c.req.header('cf-connecting-ip')||'127.0.0.1';constclientUa=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.constencoder=newTextEncoder();constdata=encoder.encode(cookie);consthashBuffer=awaitcrypto.subtle.digest('SHA-256',data);consthashArray=Array.from(newUint8Array(hashBuffer));constcookieHash=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.constcurrentUrl=newURL(c.req.url);constcacheUrl=newURL(`${currentUrl.origin}/_internal_auth_cache`);cacheUrl.searchParams.set('app_id',appId);cacheUrl.searchParams.set('cookie_hash',cookieHash);constcacheKey=newRequest(cacheUrl.toString());letresponse=awaitcache.match(cacheKey);if(!response){constisDev=c.env.APP_ENV==='development';// In production, we use Service Bindings via an internal-only URL.// In development, we use global fetch to communicate with the local Derbent port.constauthUrl=isDev
? `http://localhost:7777/internal/verify?app_id=${appId}`
: `https://auth.internal/internal/verify?app_id=${appId}`;constfetchReq=newRequest(authUrl,{headers: {Cookie: cookie,'Derbent-Client-IP': clientIp,'Derbent-Client-UA': clientUa,},});response=isDev
? awaitfetch(fetchReq)
: awaitc.env.DERBENT_SERVICE.fetch(fetchReq);if(response.ok){// Cache the response against our unique cacheKeyawaitcache.put(cacheKey,response.clone());}}returnresponse;}

Note: Derbent sends Vary: Cookie and Cache-Control: private, max-age=60 by default.

3. Internal API Reference

Consuming apps communicate with Derbent internally via Service Bindings.

GET /internal/verify

Verifies the session cookie and returns the user's session data.

Request:

  • Query:?app_id=your_app_id (e.g., hodan, sso)
  • Headers:
    • Cookie: The raw cookie string from the user's request.
    • Derbent-Client-IP: The user's IP (for hijack protection).
    • Derbent-Client-UA: The user's User-Agent (for hijack protection).

Response (200 OK):

{
"userId": "018f3a5b-7b2a-7c81-9d4f-123456789abc",
"email": "user@example.com",
"appId": "hodan",
"createdAt": 1709654321000,
"ip": "203.0.113.42",
"userAgent": "Mozilla/5.0...",
"tokenVersion": 1,
"data": {} // Custom app-specific metadata
}

Errors: Returns 401 Unauthorized or 403 Forbidden if the session is invalid, expired, or tied to a different IP/UA context.

POST /internal/logout

Destroys the current active session.

Request:

  • Query:?app_id=your_app_id
  • Headers: Same as /internal/verify

Response (200 OK):

{
"success": true
}

Admin Dashboard API

Derbent exposes privileged endpoints to manage users and system security. These endpoints must be accessed with an Authorization: Bearer <DERBENT_API_KEY> header.

Users Management

EndpointMethodDescription
/admin/usersGETList users (?page=1&limit=20&search=email@)
/admin/users/:idGETGet a single user's detailed profile
/admin/users/:idPATCHUpdate user metadata, app, or email status
/admin/users/:id/passwordPOSTForce reset a user's password
/admin/users/:id/2faDELETEDisable 2FA
/admin/users/:idDELETEPermanently delete a user & audit logs

Admin Dashboard Secret

Generate admin dashboard secret with this command:

openssl rand -hex 32

For Local Development

DERBENT_API_KEY=your_generated_hex_string_here

For Production

npx wrangler secret put DERBENT_API_KEY

Testing

npm run test

Architecture & Decisions

  • No JWTs: Opaque tokens only.
  • Stateful: Session data stored in Cloudflare KV.
  • Root Domain Cookies: Scoped to .yourdomain.com for cross-subdomain SSO.
  • Isolation: Supports both Global sso accounts and app-specific accounts.

Security & Business Logic

  1. SSO Priority: Once an sso account exists for an email, app-specific accounts for that email cannot be created.
  2. Rate Limiting: Protects against brute force.
  3. Audit Logging: Every action is recorded in the D1 audit_logs table.
  4. Hijack Prevention: Sessions are bound to User-Agent and IP.

Use Cases

Derbent works well for:

• SaaS apps on Cloudflare Workers • Multi-subdomain applications • Edge-native APIs • Self-hosted authentication systems • Replacing Auth0 for Workers projects

Assets


About

Self-hosted authentication for Cloudflare Workers. Session-based auth with KV + D1, cross-subdomain SSO, GitHub OAuth, and audit logging — without JWT complexity.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors