OpenBotAuth is a distributed system for bot authentication and policy enforcement using HTTP Message Signatures (RFC 9421) and JWKS (JSON Web Key Sets).
┌─────────────────────────────────────────────────────────────────┐
│ Internet │
└────────────┬────────────────────────────────────┬────────────────┘
│ │
│ HTTP + Signature │ HTTPS
│ │
┌────▼────┐ ┌───▼────┐
│ Bot │ │ GitHub │
│ CLI │ │ OAuth │
└────┬────┘ └───┬────┘
│ │
│ Signed Request │ OAuth Flow
│ │
┌────▼────────────────────────────────────▼─────┐
│ NGINX (Reverse Proxy) │
│ with auth_request module │
└────┬──────────────────────────────────┬───────┘
│ │
│ Verify Request │ Forward Request
│ │
┌────▼────────┐ ┌───▼──────────┐
│ Verifier │◄───JWKS Cache─────┤ Registry │
│ Service │ │ Service │
│ (RFC 9421) │ │ (JWKS) │
└────┬────────┘ └───┬──────────┘
│ │
│ Redis (nonce) │ Neon DB
│ │
┌────▼────────┐ ┌───▼──────────┐
│ Redis │ │ Neon │
│ (Cache) │ │ Postgres │
└─────────────┘ └──────────────┘
│
┌────▼────────┐
│ MCP Server │
│ (Policy) │
└─────────────┘
User → GitHub OAuth → Registry Service → Neon DB
↓
Generate Session
↓
Return to Portal
- User clicks "Login with GitHub" in portal
- Registry service redirects to GitHub OAuth
- GitHub returns with authorization code
- Registry service exchanges code for user info
- Creates/updates user and profile in Neon
- Generates session token and sets cookie
- Redirects to portal
CLI/Portal → Generate Ed25519 KeyPair (client-side)
↓
Extract Public Key
↓
Convert to JWK
↓
POST /agents → Registry Service → Neon DB
↓
Return Agent ID + JWKS URL
- Client generates Ed25519 keypair locally
- Extracts public key and converts to JWK format
- Sends public key to registry service
- Registry stores in
agentstable with JSONB - Returns agent ID and JWKS endpoint URL
- Client saves private key securely
Bot → Sign Request (RFC 9421)
↓
Add Headers:
- Signature-Input
- Signature
- Signature-Agent (Structured Dictionary entry pointing to JWKS; legacy URL supported)
↓
HTTP Request → NGINX
↓
auth_request → Verifier Service
↓
Parse Signature
↓
Check Nonce (Redis)
↓
Fetch JWKS (cache/fresh)
↓
Verify Signature
↓
Check Directory Trust
↓
Return Verdict + Headers
↓
NGINX adds X-OBAuth-* headers
↓
Forward to WordPress
↓
WordPress Plugin evaluates policy
↓
Allow / 402 Payment / 403 Deny
WordPress → 402 Payment Required
↓
Headers:
- OpenBotAuth-Price
- OpenBotAuth-Request-Hash
- Link: rel="payment"
↓
Bot → Follow payment link
↓
Complete payment
↓
Receive receipt token
↓
Retry request with:
- OpenBotAuth-Receipt header
↓
WordPress → Verify receipt
↓
200 OK with full content
Purpose: JWKS hosting, agent management, GitHub OAuth
Technology: Node.js + Express + Neon Postgres
Endpoints:
GET /jwks/{username}.json- User JWKSPOST /agent-activity- Log activityGET /auth/github- OAuth initiateGET /auth/github/callback- OAuth callbackGET /auth/session- Session infoPOST /auth/logout- Logout
Database Tables:
users- User accounts (GitHub OAuth)profiles- Extended profiles with Web Bot Auth metadatapublic_keys- Current public keyskey_history- Historical keys (rotation)agents- Bot agents with JWK public keysagent_activity- HTTP activity logssessions- OAuth sessions
Security:
- Session tokens: 32-byte cryptographically random
- Sessions expire after 30 days
- SQL injection prevention via parameterized queries
- HTTPS required in production
Purpose: RFC 9421 signature verification, nonce replay protection
Technology: Node.js + Express + Redis + web-bot-auth library
Endpoints:
POST /authorize- Verify request signature
Process:
- Extract signature headers from proxied request
- Parse
Signature-InputandSignatureheaders - Extract nonce and check for replay (Redis SET NX)
- Fetch JWKS from
Signature-Agententry (dictionary member or legacy URL) with caching - Verify signature using public key
- Check clock skew (±300s default)
- Validate directory trust
- Return verdict with X-OBAuth-* headers
Caching:
- JWKS: Redis with ETag/Cache-Control support (1 hour default)
- Nonce: Redis with TTL (10 minutes default)
Headers Set:
X-OBAuth-Verified: 1X-OBAuth-Agent-KID: {kid}X-OBAuth-Agent-JWKS: {jwks_url}X-OBAuth-Intent: {intent}X-OBAuth-Pay-State: {none|required|ok}
Purpose: Policy engine, content gating, payment flow
Technology: PHP 8.2+
Hooks:
template_redirect(priority 0) - Early gatethe_content- Teaser shaping- REST:
/wp-json/openbotauth/v1/policy- Policy export
Policy Evaluation:
- Check
X-OBAuth-Verifiedheader - Load policy from options (YAML)
- Match rules against:
- Post tags/categories
- Date/time
- Custom fields
- Apply effect:
allow→ 200 OKpay→ 402 with payment headersdeny→ 403 Forbiddenrate_limit→ 429 Too Many Requeststeaser→ 200 with truncated content
Payment Headers (402 response):
OpenBotAuth-Price: 10.00 USD
OpenBotAuth-Request-Hash: sha256(method|path|created|kid)
Link: <https://pay.example.com/i/{hash}>; rel="payment"
Receipt Verification:
- Parse
OpenBotAuth-Receiptheader - Verify JWT signature or call payment provider API
- Check request hash matches
- Set
X-OBAuth-Pay-State: ok
Purpose: Expose policy/payments/meter tools for agent interop
Technology: Node.js + MCP SDK + Express
Tools:
policy.apply(path, method, jwks_host, kid, time)→ effect/price/unlockpayments.create_intent(request_hash, amount, currency)→ pay_url/receiptmeter.ingest(event)→ ok
Transport: Streamable HTTP + SSE fallback
Auth: HTTP Message Signatures or static bearer token
Purpose: Demo crawler with RFC 9421 signing and 402 handling
Technology: Node.js + Commander + crypto
Commands:
oba-bot fetch <url>- Fetch with signatureoba-bot keygen- Generate keypairoba-bot sign <url>- Sign request (dry-run)
Process:
- Load/generate Ed25519 keypair
- Build signature base (RFC 9421)
- Sign with private key
- Add headers: Signature-Input, Signature, Signature-Agent (dictionary format preferred)
- Send HTTP request
- Handle 402:
- Parse Link header
- Follow payment URL
- Obtain receipt
- Retry with OpenBotAuth-Receipt
Purpose: Agent discovery and A2A protocol stub
Technology: Node.js + Express (static files)
Endpoints:
GET /.well-known/agent-card.json- Agent cardPOST /a2a/tasks/create- Create task (stub)GET /a2a/tasks/{id}/events- SSE events (stub)
Agent Card Format:
{
"agent": {
"name": "OpenBotAuth Verifier",
"version": "0.1.0"
},
"endpoints": {
"a2a": "https://example.com/a2a",
"mcp": "https://example.com/mcp"
},
"auth": {
"http-signatures": {
"signature-agent": "required",
"alg": ["ed25519"]
}
},
"capabilities": [
"policy.apply",
"payments.create_intent",
"meter.ingest"
]
}GitHub (Identity Provider)
↓
User (Profile Owner)
↓
Agent (Bot with Keypair)
↓
Signature (Proof of Identity)
↓
Verifier (Trust Validation)
↓
Policy (Access Control)
- User generates new keypair
- Updates agent public key via CLI/portal
- Old key moved to
key_historywithis_active=false - New key becomes active
- JWKS endpoint serves new key
- Old signatures fail verification
- Bot updates to new private key
- Bot generates unique nonce (32 bytes base64url)
- Includes in
Signature-Inputheader - Verifier checks Redis:
SET nonce:{nonce} 1 EX 600 NX - If exists → reject (replay attack)
- If new → accept and cache for 10 minutes
- Verifier resolves JWKS URL from
Signature-Agent(dictionary member or legacy URL) - Checks against
OB_TRUSTED_DIRECTORIESenv var - Only trusted directories allowed
- Prevents rogue JWKS servers
# Start all services
docker-compose up
# Services:
# - Registry: http://localhost:8080
# - Verifier: http://localhost:8081
# - MCP: http://localhost:8082
# - A2A: http://localhost:8083
# - WordPress: http://localhost:8000
# - Redis: localhost:6379Registry Service:
- Deploy to Fly.io, Railway, or Kubernetes
- Set environment variables
- Connect to Neon Postgres
- Use Redis for sessions (optional)
Verifier Service:
- Deploy as proxy sidecar or separate service
- Must have low latency to origin
- Requires Redis for nonce cache
- Set trusted directories
WordPress:
- Install plugin from WordPress.org or search "OpenBotAuth" in Plugins → Add New
- Configure verifier URL and policy via Settings → OpenBotAuth
- Optional: Configure NGINX auth_request for edge verification
-
Registry:
- JWKS endpoint response time
- Agent creation rate
- Session creation/expiration
- Database query performance
-
Verifier:
- Signature verification success/failure rate
- Nonce replay attempts
- JWKS cache hit/miss ratio
- Average verification latency
-
WordPress:
- Policy evaluation time
- 402 response rate
- Receipt verification success rate
- Content access patterns
All services use structured JSON logging:
{
"timestamp": "2025-11-16T12:00:00Z",
"level": "info",
"service": "verifier",
"message": "Signature verified",
"kid": "abc123",
"agent": "https://registry.example.com/jwks/username.json",
"duration_ms": 45
}Never log:
- Full signatures
- Private keys
- Session tokens
- Payment receipts
Always log:
- KIDs (key IDs)
- JWKS URLs
- Request hashes
- Verification results
- Registry: Stateless, scale behind load balancer
- Verifier: Stateless, share Redis for nonce cache
- MCP: Stateless, scale independently
- WordPress: Standard WP scaling (caching, CDN)
- JWKS: 1 hour cache with ETag support
- Nonce: 10 minute TTL in Redis
- Sessions: 30 day expiration
- Content: Vary on Signature-Agent (dictionary/legacy) + Pay-State
- Neon: Auto-scaling Postgres
- Indexes: On user_id, agent_id, session_token, username
- Partitioning: Consider for agent_activity (by timestamp)
- Edge Deployment: Deploy verifier to edge (Cloudflare Workers, Fastly Compute)
- Payment Integration: Stripe, x402, Locus
- Advanced Policy: Rate limiting, quota management
- Analytics Dashboard: Real-time bot activity monitoring
- Multi-directory: Support multiple trusted registries
- Key Rotation Automation: Automatic key rotation with overlap period
- Webhook Support: Notify on agent activity, policy violations
- Audit Logs: Immutable audit trail for compliance