Proof-of-concept demos showing RFC 9421 HTTP Message Signatures for agent authentication:
- Unsigned agent → teaser or 402 Payment Required
- Signed agent (Ed25519 + JWKS) → full content
This repository contains standalone demo applications that integrate with the OpenBotAuth ecosystem.
These demos prove a simple concept: agents that cryptographically identify themselves get full access, while unsigned agents see limited content:
- Unsigned fetch → Origin returns teaser (first N words) or 402 response
- Signed fetch → Origin validates signature via RFC 9421, returns full content with
X-OBA-Decision: allow
No CDN lock-in. The origin server (WordPress + OpenBotAuth plugin) performs verification using the OpenBotAuth verifier service.
Command-line demo showing unsigned vs signed HTTP requests:
# Unsigned request (gets teaser or 402)
python demo_agent.py --mode unsigned
# Signed request (gets full content)
python demo_agent.py --mode signedFeatures:
- RFC 9421 HTTP Message Signatures
- Ed25519 signing
- Clear terminal output showing headers and content differences
- Optional LangChain integration for content summarization
Interactive web UI demonstrating signed fetch with visual diff:
- Enter any URL protected by OpenBotAuth
- Toggle between unsigned/signed modes
- See request headers, response status, and body preview
- Visual diff showing added signature headers
Cryptographic agent-to-merchant payments — Full Documentation | Demo Video
Shows how commerce works when users have their own AI agents. Pete and Penny are user-owned agents (like browser extensions or OS services) that shop and pay on the user's behalf using cryptographic signatures.
What it demonstrates:
- User-Owned Agents: AI agents that live client-side and act on behalf of users, not merchants
- Cryptographic Identity: RFC 9421 HTTP Message Signatures prove agent identity without sessions/cookies
- Consent Proofs: Application-level signatures prove user authorization for specific actions
- Triple-Layer Signing: HTTP signature + 2 object signatures (consent + payment) with shared nonce
- Origin-First Verification: Merchants verify signatures directly using OpenBotAuth (no CDN/proxy)
- Live Flow Visualization: 15-step sequence diagram shows the complete cryptographic handshake
# Start backend (http://localhost:8090)
pnpm dev:tap-voice-backend
# Start frontend (http://localhost:5175)
pnpm dev:tap-voice-frontendFlow:
- User's shopping agent (Pete) adds items to cart
- User initiates checkout → Pete hands off to payment agent (Penny)
- User authorizes payment → Penny executes cryptographic flow:
- Records consent proof with timestamp
- Generates shared nonce for all signature layers
- Signs consent proof object (Ed25519)
- Signs payment request object (Ed25519)
- Signs HTTP request (RFC 9421)
- Sends to merchant with all three signatures
- Merchant verifies:
- HTTP signature via OpenBotAuth verifier
- Fetches agent's public key from JWKS
- Validates consent + payment object signatures
- Checks nonce consistency across all layers
- Authorizes payment with Visa
- Order confirmed
Security: Shared nonce prevents replay attacks, 8-minute time windows, Ed25519 signatures, origin-side verification.
See TAP_VOICE_DEMO.md for complete setup, architecture, ElevenLabs configuration, and troubleshooting.
📚 For fastest setup, see QUICKSTART.md — includes automated key configuration.
- Node.js 20+ and pnpm 8+
- Python 3.10+ (for agent demo)
- Ed25519 keypair registered with OpenBotAuth registry
- Target URL with OpenBotAuth plugin (default:
https://blog.attach.dev/?p=6)
Option A: Use existing OpenBotAuth registry (recommended)
Visit the OpenBotAuth Registry Portal or run locally:
# In the main openbotauth repo
cd packages/bot-cli
pnpm install
pnpm dev keygen
# Follow prompts to generate and register keysOption B: Generate standalone keys (for testing)
# Using OpenSSL
openssl genpkey -algorithm ed25519 -out private_key.pem
openssl pkey -in private_key.pem -pubout -out public_key.pemAutomatic (Recommended):
If you downloaded keys from the OpenBotAuth website:
# For root .env (widget demo)
node scripts/parse-keys.js path/to/openbotauth-keys-username.txt
# For specific app directories (uses .env.example as template)
node scripts/parse-keys.js path/to/openbotauth-keys-username.txt apps/tap-voice-agents-backend
# For Python agent
cd examples/langchain-agent
python parse_keys.py path/to/openbotauth-keys-username.txtNote: When using an app-specific directory, the script will:
- Use that app's
.env.exampleas a template - Replace only the
OBA_*placeholders - Leave other fields for you to fill in (like ElevenLabs API keys)
Manual:
# Copy example config
cp .env.example .env
# Edit .env and add your keys
# - OBA_PRIVATE_KEY_PEM: Your Ed25519 private key (PEM format)
# - OBA_PUBLIC_KEY_PEM: Your Ed25519 public key (PEM format)
# - OBA_KID: Your key ID from the registry
# - OBA_SIGNATURE_AGENT_URL: Your JWKS URL from the registrycd examples/langchain-agent
# Create virtual environment
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Configure keys (if not already done)
python parse_keys.py path/to/openbotauth-keys-username.txt
# Test unsigned (should get teaser/402)
python demo_agent.py --mode unsigned --url https://blog.attach.dev/?p=6
# Test signed (should get full content)
python demo_agent.py --mode signed --url https://blog.attach.dev/?p=6Expected output:
- Unsigned:
Status: 200with[TEASER]indicator, limited content - Signed:
Status: 200withX-OBA-Decision: allowheader, full content
Note: Some origin servers may be slow or rate-limited. If you encounter timeouts, try alternative URLs or wait a moment before retrying.
# Install all dependencies
pnpm install
# Configure keys for backend (if not already done)
node scripts/parse-keys.js path/to/openbotauth-keys-username.txt
# Terminal 1: Start backend
pnpm dev:widget-backend
# Runs on http://localhost:8089
# Terminal 2: Start frontend
pnpm dev:widget-frontend
# Runs on http://localhost:5174Open http://localhost:5174 in your browser:
- Enter URL:
https://blog.attach.dev/?p=6 - Click "Fetch Unsigned" → see teaser
- Click "Fetch Signed" → see full content + signature headers
sequenceDiagram
participant Agent
participant Origin as WordPress + OBA Plugin
participant Verifier as verifier.openbotauth.org
Note over Agent,Verifier: Unsigned Request
Agent->>Origin: GET /protected (no signature)
Origin-->>Agent: 200 OK (teaser content)
Note over Agent,Verifier: Signed Request
Agent->>Agent: Sign with Ed25519 private key
Agent->>Origin: GET /protected + Signature headers
Origin->>Verifier: POST /verify (signature + JWKS URL)
Verifier->>Verifier: Fetch JWKS, verify signature
Verifier-->>Origin: {verified: true, agent: {...}}
Origin-->>Agent: 200 OK (full content) + X-OBA-Decision: allow
Signature Format (RFC 9421):
Signature-Input: sig1=("@method" "@authority" "@path");created=1234567890;expires=1234568190;nonce="abc123";keyid="key-001";alg="ed25519"
Signature: sig1=:SGVsbG8gV29ybGQK...:
Signature-Agent: https://registry.openbotauth.org/jwks/username.json
Components signed:
@method: HTTP method (GET, POST, etc.)@authority: Host + port (e.g.,blog.attach.dev)@path: Path + query string (e.g.,/?p=6)
Key details:
- Algorithm: Ed25519 (EdDSA)
- Signature encoding: base64 (not base64url)
- Nonce: base64url-encoded random 16 bytes
- Time window:
(expires - created) ≤ 300s
Implementation features:
- Automatic redirect handling: When the origin returns 3xx, the request is re-signed for the new URL
- Timeout resilience: 30-second timeout prevents hanging on slow origins
- Clock skew tolerance: ±5 minutes allowed between client and verifier
- Header size consideration: Large signatures may require NGINX/Apache configuration adjustments
openbotauth-demos/
├── README.md # This file
├── TAP_VOICE_DEMO.md # TAP Voice Agents comprehensive guide
├── QUICKSTART.md # Fast setup guide
├── SECURITY_AUDIT.md # Dependency security review
├── LICENSE # Apache 2.0
├── package.json # Root workspace
├── pnpm-workspace.yaml # pnpm workspace config
├── .env.example # Environment template
├── scripts/
│ ├── parse-keys.js # Auto-configure .env from key file
│ └── README.md # Key parser documentation
├── examples/
│ └── langchain-agent/ # Python demo
│ ├── demo_agent.py # CLI tool
│ ├── signed_fetch.py # RFC 9421 signer
│ ├── parse_keys.py # Python key parser
│ ├── requirements.txt
│ └── README.md
├── packages/
│ └── signing-ts/ # Shared TypeScript signing library
│ ├── src/
│ │ ├── index.ts # Main exports
│ │ ├── rfc9421.ts # RFC 9421 canonicalization
│ │ ├── ed25519.ts # Ed25519 crypto
│ │ └── types.ts # TypeScript interfaces
│ └── test/
│ └── signing.test.ts # Unit tests + golden vector
└── apps/
├── widget-backend/ # Express API server
│ └── src/
│ ├── server.ts # /api/fetch endpoint
│ ├── config.ts # Environment config
│ ├── logger.ts # Secure logging
│ └── types.ts # Type definitions
├── widget-frontend/ # React UI
│ └── src/
│ ├── App.tsx # Main component
│ └── components/
│ └── HeadersDiff.tsx # Signature headers diff view
├── tap-voice-agents-backend/ # TAP Voice Agents backend
│ ├── src/
│ │ ├── server.ts # Express app
│ │ ├── routes/ # API endpoints
│ │ ├── services/ # Business logic
│ │ ├── tap/ # TAP object builders
│ │ └── events/ # SSE event emitter
│ ├── ELEVENLABS_SETUP.md # ElevenLabs agent configuration
│ └── ELEVENLABS_WEBHOOKS.json # Tool definitions
└── tap-voice-agents-frontend/ # TAP Voice Agents frontend
└── src/
├── App.tsx # Main application
├── components/ # UI components
├── hooks/ # React hooks (SSE)
└── utils/ # Helper functions
- RFC 9421 — HTTP Message Signatures
- RFC 7517 — JSON Web Key (JWK)
- Ed25519 — EdDSA signature scheme
- OpenBotAuth — Main project (registry, verifier, WordPress plugin)
- SECURITY_AUDIT.md — Cryptographic dependency verification and security review
Symptoms: Signed requests return 403 or teaser instead of full content
Checks:
- Clock skew: Ensure system time is accurate (NTP sync)
- Key registration: Verify your public key is in the registry JWKS
- JWKS URL: Confirm
OBA_SIGNATURE_AGENT_URLis accessible and returns valid JWKS - Signature format: Check that signature uses base64 (not base64url)
# Test JWKS accessibility
curl https://registry.openbotauth.org/jwks/your-username.json
# Should return:
# {
# "keys": [{"kty": "OKP", "crv": "Ed25519", "kid": "...", "x": "..."}],
# "client_name": "..."
# }# Ensure virtual environment is activated
source .venv/bin/activate
# Reinstall dependencies
pip install --upgrade -r requirements.txtIf port 8089 is in use:
# Edit .env
WIDGET_PORT=8090
# Restart backend
pnpm dev:widget-backendIf you encounter 400 errors when sending signed requests, the server may have header size limits that are too restrictive for RFC 9421 signatures.
NGINX:
# In http or server block
large_client_header_buffers 4 32k;Apache:
# In httpd.conf or .htaccess
LimitRequestFieldSize 32768The demos use a 30-second timeout. If origin servers are slow:
- Try alternative test URLs with reliable response times
- Wait and retry if the origin is rate-limiting
- Increase timeout in backend config if needed (edit
apps/widget-backend/src/server.ts)
# Install all dependencies
pnpm install
# Build all packages
pnpm build
# Run tests
pnpm test
# Clean build artifacts
pnpm cleanThe TypeScript and Python implementations should produce identical signatures:
# Run golden vector test
cd packages/signing-ts
pnpm test
# Compare with Python
cd ../../examples/langchain-agent
pytest test_signing.py # (if tests are added)Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
Apache 2.0 - See LICENSE for details.
- Built on RFC 9421 HTTP Message Signatures
- Integrates with OpenBotAuth ecosystem
- Demo site provided by Attach
Made for the agent economy 🤖