DevAuthKit Documentation • v1.6.0 Production Ready

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.

npm install devauthkit
Jump to Quickstart →

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"));
Zero-Setup Dev Mode
When email credentials are not configured, DevAuthKit automatically outputs 6-digit OTP codes and 1-click magic links directly into your terminal for immediate local testing.

Interactive Security Sandbox

Inspect authentication handshakes and cryptographic proof generation directly in your browser.

Server Console Output
Ready. Click "1. Dispatch OTP" to simulate zero-config dev mode dispatch.

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.

Cryptographic Proof Inspector
Click "Generate P-256 Key" to compute an RFC 7638 thumbprint in browser memory...

Test WebAuthn platform authenticator support for Touch ID, Face ID, and Windows Hello.

WebAuthn Credential Inspector
Click "Detect Platform Authenticator" to inspect hardware biometric support...

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.

Enterprise SSO Architecture & Zero Enterprise Tax
• Zero Enterprise Tax: Replaces WorkOS ($125/conn/mo) and Auth0 Enterprise ($800+/mo) with a completely self-hosted, MIT-licensed engine.
• 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)

server.js
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-sso-setup.js
// 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

client.js
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.

Core SCIM 2.0 Architecture & Deprovisioning Defense
• Instant Deprovisioning Cascade: Setting 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)

server.js
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

admin-scim-setup.js
// 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

IdP Deprovisioning Flow (RFC 7644 PATCH)
// 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.

Core Webhook Security Architecture
• Svix / Stripe Standard: Headers format signature as 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

server.js
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)

receiver.js
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).

Core Architecture & Security Highlights
• Tenant-Scoped JWTs: Switching workspaces issues a token scoped to 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

server.js
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)

WorkspaceSelector.jsx
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.

Cryptographic Binding Mechanism
1. Client generates an ephemeral ECDSA P-256 keypair in device memory.
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.js & client.js
// 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.

Passkeys Server Configuration
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.

OTP Endpoints & Usage
// 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": "..." } }

Rotating Refresh Tokens & Revocation

Short-lived access tokens paired with rotating httpOnly cookies and token family reuse detection.

Refresh Token Options
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.

Rate Limit Configuration
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.

requireRole Middleware
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.

OAuth Setup
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.

devauthkit/hono worker.js
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.

Prisma Integration
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.

Drizzle Integration
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.

Mongoose Integration
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.

Redis Setup
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.

useAuth Hook Example
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.

DevAuthClient Usage
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.

.env Template
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 — — —