DevAuthKit Complete Documentation
DevAuthKit is a zero-config, plug-and-play self-hosted authentication toolkit for Node.js, Express, and Cloudflare Workers (Hono). It provides hardware-bound RFC 9449 DPoP token defense, WebAuthn passkeys, passwordless email OTP, rotating refresh tokens, and database adapters for Prisma, Drizzle, and Mongoose with zero monthly active user (MAU) billing limits.
Quickstart Guide
Mount a production-ready authentication engine in less than 3 minutes.
const express = require("express");
const devAuthKit = require("devauthkit");
const app = express();
// Initialize DevAuthKit (Zero-config dev mode prints OTP to console)
devAuthKit(app, {
jwtSecret: process.env.JWT_SECRET || "your-secret-key",
sessionSecret: process.env.SESSION_SECRET || "your-session-key",
corsOrigin: ["http://localhost:3000", "http://localhost:5173"],
devMode: true
});
// Protect routes with triple-strategy requireAuth middleware
app.get("/api/dashboard", devAuthKit.requireAuth, (req, res) => {
res.json({ message: "Authenticated", user: req.user });
});
app.listen(3000, () => console.log("DevAuthKit server ready on http://localhost:3000"));
Interactive Security Sandbox
Inspect authentication handshakes and cryptographic proof generation directly in your browser.
Uses browser window.crypto.subtle to generate an ECDSA P-256 keypair, computes RFC 7638 SHA-256 thumbprint (cnf.jkt), and binds your access token directly to this device.
Test WebAuthn platform authenticator support for Touch ID, Face ID, and Windows Hello.
Enterprise SSO (SAML 2.0 & Enterprise OIDC)
Zero-fee, self-hosted enterprise Single Sign-On engine for Okta, Microsoft Entra ID (Azure AD), Google Workspace, OneLogin, and PingFederate. Features pure-JS XML Exclusive Canonicalization (C14N), RSA-SHA256 signature verification, dual rolling X.509 certificate rollover, and strict defenses against XXE, XML Signature Wrapping (XSW), and anti-replay attacks.
• Zero Native C++ Dependencies: 100% pure JavaScript XML canonicalization (C14N:
http://www.w3.org/2001/10/xml-exc-c14n#) and cryptographic verification — runs seamlessly on Node.js, Express, and Cloudflare Workers (edge compute).• Multi-Tenant SAML 2.0 Service Provider (SP): Dynamic SP Metadata XML generation at
/auth/sso/saml/:id/metadata.xml with entity ID and ACS URL.• Universal Domain Routing: Automatically resolves enterprise IdPs via
resolveSso({ email }) with DNS TXT ownership verification (_devauth-verify.<domain>).• Strict Cryptographic Security: XML Signature Wrapping (XSW) elimination, anti-replay assertion ID tracking, 5-minute clock skew tolerance, and dual rolling X.509 certificate rollover.
• JIT User & Org Provisioning: Automatic tenant account creation, organization binding, and tenant-scoped JWT generation upon SAML assertion.
1. Server Setup (Express & Hono)
const express = require("express");
const devAuthKit = require("devauthkit");
const { MemorySsoStore } = require("devauthkit/sso");
const app = express();
devAuthKit(app, {
jwtSecret: process.env.JWT_SECRET,
sso: true, // ✨ Enables SAML 2.0 / OIDC SSO & domain routing
ssoStore: new MemorySsoStore(), // Or CloudflareD1SsoStore for Workers
spEntityId: "https://api.yourdomain.com/auth/sso/sp",
spAcsUrl: "https://api.yourdomain.com/auth/sso/saml/acs"
});
2. Registering an Enterprise IdP Connection
// Admin endpoint to register Okta, Azure AD, or Google Workspace connection
const res = await fetch("https://api.yourdomain.com/auth/sso/connections", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": "Bearer " + adminJwt },
body: JSON.stringify({
name: "Acme Corp Okta",
type: "saml",
entityId: "http://www.okta.com/exk123456789",
ssoUrl: "https://acme.okta.com/app/acmeprod_app/exk123456789/sso/saml",
certificate: "-----BEGIN CERTIFICATE-----\nMIIDpDCCAoygAwIBAgIGAX...\n-----END CERTIFICATE-----",
allowedDomains: ["acme.com"],
organizationId: "org_acme_corp"
})
});
const { connection } = await res.json();
// SP Metadata XML is now live at: /auth/sso/saml/${connection.id}/metadata.xml
// ACS Endpoint is now live at: /auth/sso/saml/${connection.id}/acs
3. Frontend Universal Domain Routing
import { createDevAuthClient } from "devauthkit/client";
const devauth = createDevAuthClient({ baseUrl: "https://api.yourdomain.com" });
async function handleLogin(email) {
// Automatically detects if email domain belongs to an enterprise SSO connection
const sso = await devauth.resolveSso({ email });
if (sso.isEnterprise) {
// Redirects browser to Okta / Azure AD with signed Deflated SAML AuthnRequest
devauth.loginWithSso(sso.connection.id, { redirectUrl: "/dashboard" });
} else {
// Fall back to Passwordless OTP, Magic Link, or Biometric Passkey
await devauth.sendOtp({ email });
}
}
4. Enterprise SSO REST Endpoints
| Method | Endpoint | Description |
|---|---|---|
POST |
/auth/sso/connections |
Register an IdP (SAML 2.0 / OIDC) with dual certificates & domain bindings |
GET |
/auth/sso/connections |
List all active enterprise SSO connections |
GET |
/auth/sso/resolve?domain=acme.com |
Resolve corporate domain to active IdP connection (public endpoint) |
GET |
/auth/sso/saml/:id/metadata.xml |
Dynamic SP Metadata XML with entity ID and ACS URL |
GET |
/auth/sso/saml/:id/login |
Generates Deflate-compressed Base64 AuthnRequest & redirects to IdP |
POST |
/auth/sso/saml/:id/acs |
Assertion Consumer Service (C14N verification, JIT provisioning & JWT issue) |
POST |
/auth/sso/domains/:id/verify |
Validate DNS TXT record (_devauth-verify.<domain>) for domain ownership |
SCIM 2.0 Directory Sync (RFC 7643 / RFC 7644)
Automated user and group lifecycle provisioning compliant with standard RFC 7643 and RFC 7644 protocols. Enables Okta, Microsoft Entra ID (Azure AD), and OneLogin to automatically provision accounts, update profile attributes, sync group memberships, and trigger instant deprovisioning cascades when employees leave the company.
active: false in Okta immediately removes workspace memberships and purges all active refresh tokens and sessions across all user devices.• High-Entropy Bearer Tokens: SCIM directories authenticate via 256-bit cryptographically secure tokens (
scim_...) stored securely as SHA-256 hashes.• RFC 7643 / 7644 Compliance: Implements official schemas, ServiceProviderConfig, Users, and Groups endpoints conforming to enterprise IdP requirements.
• RFC 7644 PATCH Operations: Parses complex JSON-Patch replace operations (e.g.,
operations: [{ op: "replace", path: "active", value: false }]).• Multi-Tenant Storage Adapters: Zero-latency in-memory and Cloudflare D1 SQL adapters support directory-to-organization tenancy binding.
1. Server Setup (Express & Hono)
const devAuthKit = require("devauthkit");
const { MemorySsoStore } = require("devauthkit/sso");
devAuthKit(app, {
jwtSecret: process.env.JWT_SECRET,
scim: true, // ✨ Mounts /scim/v2 endpoints with application/scim+json support
ssoStore: new MemorySsoStore()
});
2. Provisioning SCIM Directory for an Enterprise Customer
// Create a SCIM directory for an enterprise organization
const res = await fetch("https://api.yourdomain.com/auth/scim/directories", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": "Bearer " + adminJwt },
body: JSON.stringify({
name: "Acme Corp Okta SCIM",
organizationId: "org_acme_corp"
})
});
const { directory, token } = await res.json();
// Give these credentials to the Enterprise IT Administrator:
// SCIM 2.0 Base URL: https://api.yourdomain.com/scim/v2
// SCIM Bearer Token: scim_live_... (only shown once)
3. Instant Deprovisioning & Session Purge Cascade
// Okta / Azure AD sends RFC 7644 PATCH request when employee departs:
// PATCH /scim/v2/Users/usr_123
// Authorization: Bearer scim_live_...
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{ "op": "replace", "path": "active", "value": false }
]
}
// DevAuthKit executes the Instant Deprovisioning Cascade:
// 1. Updates user status: active = false
// 2. Purges user from organization workspace membership
// 3. Revokes ALL active refresh tokens across all mobile/desktop devices
// 4. Dispatches cryptographically signed webhook: user.deprovisioned
4. SCIM 2.0 REST Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET |
/scim/v2/ServiceProviderConfig |
RFC 7643 metadata discovery (patch, filter, bulk support) |
GET |
/scim/v2/Schemas |
RFC 7643 Core User and Group schema definitions |
GET |
/scim/v2/Users |
Filter and paginate enterprise users (filter=userName eq "...") |
POST |
/scim/v2/Users |
Provision a new enterprise user and map to organization |
GET |
/scim/v2/Users/:id |
Retrieve single enterprise user profile |
PUT |
/scim/v2/Users/:id |
Replace enterprise user profile attributes |
PATCH |
/scim/v2/Users/:id |
Partial update (detects active: false to trigger session purge) |
DELETE |
/scim/v2/Users/:id |
Deprovision user and delete directory mapping |
GET |
/scim/v2/Groups |
List directory user groups and department memberships |
Cryptographically Signed Webhooks (HMAC-SHA256)
Enterprise outbound event delivery engine conforming to the Svix / Stripe signature standard (DevAuth-Signature: t=${timestamp},v1=${signature}). Features anti-replay tolerance windows, constant-time verification, wildcard pattern subscriptions, non-blocking asynchronous dispatch, and delivery logging.
DevAuth-Signature: t=...,v1=... over ${timestamp}.${rawBody}.• Constant-Time Verification: Uses timing-safe equality to eliminate timing side-channel exploits.
• Anti-Replay Defense: Rejects requests with timestamps outside the tolerance window (default 300s) and future clock skew.
• Zero-Downtime Secret Rolling: Verifies against any valid
v1 signature when rotating endpoint secrets.• Non-Blocking Dispatch: Dispatches via microtasks without adding latency (< 1ms) to login or organization APIs.
1. Server Configuration
const devAuthKit = require("devauthkit");
devAuthKit(app, {
jwtSecret: process.env.JWT_SECRET,
webhooks: true // ✨ Enables /auth/webhooks management & auto-event dispatch
});
2. Consuming & Verifying Webhooks (createWebhookHandler)
const { createWebhookHandler } = require("devauthkit/webhooks");
app.post("/api/webhooks/devauth", express.raw({ type: "application/json" }), createWebhookHandler({
secret: process.env.DEVAUTH_WEBHOOK_SECRET, // "whsec_..."
toleranceSeconds: 300,
onEvent: async (event) => {
switch (event.event) {
case "org.created":
await provisionTenantResources(event.data.organization);
break;
case "user.authenticated":
await syncUserAnalytics(event.data.userId);
break;
}
}
}));
3. Webhook REST Management Endpoints
| Method | Endpoint | Description |
|---|---|---|
POST |
/auth/webhooks |
Register endpoint URL and subscribed events (["org.*", "user.*"]) |
GET |
/auth/webhooks |
List registered endpoints |
GET |
/auth/webhooks/:id |
Get specific endpoint configuration |
PATCH |
/auth/webhooks/:id |
Update endpoint URL, events filter, or active status |
DELETE |
/auth/webhooks/:id |
Delete endpoint |
GET |
/auth/webhooks/:id/deliveries |
List recent delivery logs, HTTP status codes, and latencies |
POST |
/auth/webhooks/:id/test |
Dispatch an immediate webhook.test ping |
Multi-Tenancy & Organizations (B2B Workspaces)
Turnkey B2B workspace isolation, member invitations with 256-bit cryptographic token hashing, tenant-scoped JWTs, dynamic workspace switching, and org-scoped RBAC/PBAC. Eliminates steep SaaS add-on fees ($$$/month).
org_id, org_role, and resolved permissions.• Cryptographic Invitations: Secret 256-bit tokens are SHA-256 hashed before storage; raw tokens are never persisted in plaintext.
• Privilege Escalation Defense: Admins cannot grant the Owner role or demote owners; owners cannot abandon workspaces without transferring ownership.
• RFC 9449 DPoP Continuity: Preserves client hardware key thumbprints (
cnf.jkt) across workspace context shifts.
1. Express Backend Setup
const devAuthKit = require("devauthkit");
devAuthKit(app, {
jwtSecret: process.env.JWT_SECRET,
organizations: true // ✨ Enables /auth/orgs endpoints & workspace engine
});
const { requireOrg, requireOrgRole, requireOrgPermission } = devAuthKit;
// Context-scoped endpoint (resolves org from token org_id or x-organization-id header)
app.get("/api/projects", requireOrg, (req, res) => {
res.json({ workspace: req.org.name, myRole: req.orgMember.role });
});
// Granular role check (owner or admin)
app.get("/api/billing", requireOrgRole(["owner", "admin"]), (req, res) => {
res.json({ invoices: [] });
});
2. React & Next.js Hook (useOrg)
import { useOrg } from "devauthkit/react";
function WorkspaceSelector() {
const { activeOrg, organizations, switchOrg, createOrg } = useOrg();
return (
<select value={activeOrg?.id} onChange={(e) => switchOrg(e.target.value)}>
{organizations.map((org) => (
<option key={org.id} value={org.id}>
{org.name} ({org.role})
</option>
))}
</select>
);
}
RFC 9449 DPoP: Proof-of-Possession Token Binding
In standard OAuth 2.0 architectures, access tokens are Bearer tokens: anyone who extracts the token (via infostealer malware, malicious browser extensions, XSS, or proxy logs) can impersonate the user. DPoP eliminates token theft by binding credentials cryptographically to the client device.
2. Server embeds the RFC 7638 SHA-256 thumbprint (
cnf.jkt) into the JWT.3. Client computes an ephemeral single-use signature containing the HTTP method, URI, timestamp, and token hash (
ath).4. If an attacker intercepts and replays the token, the server rejects it with
401 Unauthorized.
Implementation on Server and Client
// Server configuration
devAuthKit(app, {
dpop: true, // Activates RFC 9449 binding & replay protection store
jwtSecret: process.env.JWT_SECRET
});
// Strict DPoP enforcement on sensitive routes
app.post("/api/transfer", devAuthKit.requireDPoP, (req, res) => {
res.json({ success: true, dpopThumbprint: req.dpop.jkt });
});
// Client SDK initialization
const auth = new DevAuthClient({
apiUrl: "http://localhost:3000",
dpop: true // Transparent client-side P-256 signing
});
Passkeys & WebAuthn Biometrics
Authenticate users with Touch ID, Face ID, Windows Hello, or hardware security keys without passwords.
devAuthKit(app, {
passkeys: {
rpName: "DevAuthKit App",
rpID: "myapp.com",
origins: ["https://myapp.com"]
},
passkeyStore: myDurablePasskeyStore,
passkeyChallengeStore: myAtomicChallengeStore
});
Passwordless Email OTP
Secure 6-digit codes generated using Node's cryptographically secure crypto.randomInt.
// 1. Client requests OTP
POST /auth/login
{ "email": "user@example.com" }
// Response:
{ "success": true, "message": "OTP sent", "cooldownSeconds": 60 }
// 2. Client verifies OTP
POST /auth/verify-otp
{ "email": "user@example.com", "otp": "492810" }
// Response:
{ "success": true, "token": "eyJhbGci...", "user": { "id": "...", "email": "..." } }
1-Click Magic Links
Issue single-use cryptographic verification links directly inside login emails alongside OTP codes.
devAuthKit(app, {
magicLink: true,
magicLinkExpiryMinutes: 15,
magicLinkSuccessURL: "http://localhost:3000/dashboard"
});
// Automatically handles verification at:
// GET /auth/verify-magic?token=...&email=...
Rotating Refresh Tokens & Revocation
Short-lived access tokens paired with rotating httpOnly cookies and token family reuse detection.
devAuthKit(app, {
refreshTokens: {
accessTokenExpiresIn: "15m",
refreshTokenExpiresInDays: 30
},
jwtSecret: process.env.JWT_SECRET,
sessionSecret: process.env.SESSION_SECRET
});
// Endpoints automatically mounted:
// POST /auth/refresh - Rotates cookie and issues new access token
// POST /auth/sessions/revoke - Revokes current device token family
// POST /auth/sessions/revoke-all- Revokes all sessions across all devices
Rate Limiting & Brute-Force Shield
Fixed-window limits per IP and per email with automatic account lockout after 5 failed verification attempts.
devAuthKit(app, {
rateLimit: {
requestOtp: { perEmailMax: 5, perIpMax: 30 },
verifyOtp: { perEmailMax: 10, perIpMax: 40 }
}
});
Role-Based Access Control (RBAC)
Built-in middleware for granular route authorization based on verified user claims.
const { requireAuth, requireRole } = devAuthKit;
// Restrict endpoint to admin users only
app.get("/api/admin/metrics", requireAuth, requireRole(["admin", "superadmin"]), (req, res) => {
res.json({ adminData: true, user: req.user });
});
Google & GitHub Social Login (PKCE)
Unified PKCE (S256) OAuth 2.0 flows with origin checking and verified email enforcement.
devAuthKit(app, {
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:3000/auth/google/callback",
successRedirectURL: "http://localhost:3000/dashboard"
},
github: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
callbackURL: "http://localhost:3000/auth/github/callback",
successRedirectURL: "http://localhost:3000/dashboard"
}
});
Cloudflare Workers & Hono
Deploy auth directly to the edge using Hono, Cloudflare D1 serverless SQLite, and KV storage.
import { Hono } from "hono";
import { createHonoAuth, CloudflareD1RefreshTokenStore } from "devauthkit/hono";
const app = new Hono();
export default {
async fetch(request, env, ctx) {
const auth = createHonoAuth({
jwtSecret: env.JWT_SECRET,
d1: env.DB,
refreshTokenStore: new CloudflareD1RefreshTokenStore(env.DB)
});
app.route("/auth", auth.routes);
return app.fetch(request, env, ctx);
}
};
Prisma ORM Adapter
Persist user profiles and roles seamlessly into your existing Prisma database models.
const { PrismaClient } = require("@prisma/client");
const prismaAdapter = require("devauthkit/adapters/prisma");
const prisma = new PrismaClient();
devAuthKit(app, {
userAdapter: prismaAdapter(prisma),
mapUserPayload: (user) => ({ role: user.role, plan: user.plan })
});
Drizzle ORM Adapter
Works with Drizzle ORM across PostgreSQL, MySQL, and SQLite.
const drizzleAdapter = require("devauthkit/adapters/drizzle");
const { users } = require("./schema");
devAuthKit(app, {
userAdapter: drizzleAdapter(db, users)
});
Mongoose (MongoDB) Adapter
Native MongoDB integration for Mongoose user schemas.
const mongooseAdapter = require("devauthkit/adapters/mongoose");
const User = require("./models/User");
devAuthKit(app, {
userAdapter: mongooseAdapter(User)
});
Redis Distributed Store
Share OTP states, rate limit counters, and token families across multi-instance server deployments.
const { createClient } = require("redis");
const redis = createClient();
devAuthKit(app, {
otpStore: new devAuthKit.RedisOtpStore(redis),
rateLimitStore: new devAuthKit.RedisRateLimitStore(redis)
});
React & Next.js Hooks (devauthkit/react)
Official React hooks and context provider for state synchronization across your UI.
import { AuthProvider, useAuth } from "devauthkit/react";
function Dashboard() {
const { user, isAuthenticated, isLoading, sendOTP, verifyOTP, logout } = useAuth();
if (isLoading) return <div>Loading...</div>;
if (!isAuthenticated) return <button onClick={() => sendOTP("user@dev.io")}>Login</button>;
return (
<div>
<h1>Welcome, {user.email}</h1>
<button onClick={logout}>Sign Out</button>
</div>
);
}
Universal Client SDK (devauthkit/client)
Vanilla JavaScript SDK with automatic Bearer token injection, DPoP signing, and reactive storage listeners.
import { DevAuthClient } from "devauthkit/client";
const auth = new DevAuthClient({
apiUrl: "http://localhost:3000",
dpop: true // Enables hardware-bound token protection
});
// Send and verify OTP
await auth.sendOTP("alex@dev.io");
await auth.verifyOTP("alex@dev.io", "123456");
// Automatically attaches DPoP signature + Bearer header
const res = await auth.fetch("/api/dashboard");
const data = await res.json();
Full Configuration Reference
Every configuration option available when initializing DevAuthKit.
| Option | Type | Default | Description |
|---|---|---|---|
jwtSecret |
string |
Required in prod | Secret key for signing access JWT tokens. |
sessionSecret |
string |
Required in prod | Secret key for encrypting session cookies. |
corsOrigin |
string | string[] |
[] |
Allowed frontend origins for CORS credentialed requests. |
devMode |
boolean |
false |
Prints OTP codes and magic links to terminal if email is unconfigured. |
dpop |
boolean |
false |
Enables RFC 9449 token binding & replay cache protection. |
refreshTokens |
boolean | object |
false |
Enables rotating httpOnly refresh cookies (15m access, 30d refresh). |
userAdapter |
UserAdapter |
In-memory | Database adapter (Prisma, Drizzle, Mongoose, or custom). |
magicLink |
boolean |
false |
Issues 1-click magic login links alongside OTP emails. |
passkeys |
PasskeyOptions |
Disabled | WebAuthn configuration with rpName, rpID, and origins. |
rateLimit |
RateLimitConfig |
Enabled | Fixed-window rate limits per IP and per email. |
.env Production Setup
Production environment variables template for your server.
SESSION_SECRET=super_secret_session_key_12345
JWT_SECRET=super_secret_jwt_key_12345
# Modern Delivery via Resend (Recommended)
RESEND_API_KEY=re_123456789
FROM_EMAIL=auth@yourdomain.com
# Standard SMTP Alternative
EMAIL_USER=your-email@gmail.com
EMAIL_PASS=your-app-password
# Social OAuth Credentials
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
Architecture Comparison
Feature comparison against common hosted solutions and open-source packages.
| Capability | DevAuthKit | Clerk / Auth0 | NextAuth / Auth.js | Passport.js |
|---|---|---|---|---|
| Pricing Model | 100% Free / Self-Hosted | MAU Tiers ($$$) | Free | Free |
| Setup Overhead | < 3 minutes | 5 minutes | 30+ minutes | 2+ hours |
| Enterprise SAML 2.0 SSO | ✓ Built-in (Zero Fee) | $125/conn/mo | Manual Glue | Complex XML glue |
| SCIM 2.0 Directory Sync | ✓ RFC 7643/7644 | Enterprise Tier ($$$) | — | — |
| B2B Workspaces & Orgs | ✓ Multi-Tenant + RBAC | Add-on Fee | DIY | — |
| Signed Webhooks | ✓ Svix/Stripe Standard | Cloud Dependent | — | — |
| RFC 9449 DPoP | ✓ Built-in | — | — | — |
| Biometric Passkeys | ✓ Native WebAuthn | ✓ Cloud Hosted | Manual Config | — |
| Cloudflare Edge | ✓ D1 & KV Native | — | Partial | — |
| Database Adapters | Prisma, Drizzle, Mongo | Vendor Locked | Prisma, Drizzle | Manual Glue |
| Zero-Config Dev Mode | ✓ Terminal OTP | — | — | — |