A Go implementation of Web Bot Auth (WBA), the emerging IETF standard for cryptographic bot identity based on HTTP Message Signatures (RFC 9421).
- Go 1.25+
- Docker & Docker Compose (for PostgreSQL)
docker compose up -d postgresThis starts PostgreSQL on port 5432 and runs the schema migration automatically.
With PostgreSQL:
go run ./cmd/wba-server -dsn "postgres://wba:wba@localhost:5432/wba?sslmode=disable"With in-memory store (no database needed):
go run ./cmd/wba-serverThe server starts on :8080 by default. Verify with:
curl http://localhost:8080/healthdocker compose up -dThis builds and starts both the WBA server and PostgreSQL.
# All tests (no database required — uses in-memory store)
go test ./...
# Verbose
go test ./... -v
# Single package
go test ./pkg/httpsig/... -vExercises the full flow: key generation → registration → directory fetch → signed request → rejection of unsigned request.
go run ./cmd/e2e-testgo run ./cmd/wba-keygen -out ./keysCreates keys/public.jwk and keys/private.jwk (Ed25519).
# Start server first, then:
go run ./cmd/wba-register \
-server http://localhost:8080 \
-tenant my-tenant \
-domain acme-bot.com \
-name "ACME Bot" \
-key ./keys/private.jwkcurl -H "Host: acme-bot.com" http://localhost:8080/.well-known/http-message-signatures-directory| Flag | Env Var | Default | Description |
|---|---|---|---|
-addr |
WBA_ADDR |
:8080 |
Listen address |
-dsn |
WBA_DSN |
(empty) | PostgreSQL DSN. Empty = in-memory store |
-nonce |
WBA_NONCE |
none |
Nonce policy: none, required, time-window |
-require-wba |
WBA_REQUIRE_WBA |
false |
Reject unsigned requests on /api/* |
-server-domain |
WBA_SERVER_DOMAIN |
localhost |
Managed server domain (see below) |
-admin-user |
WBA_ADMIN_USER |
admin |
Admin dashboard username |
-admin-pass |
WBA_ADMIN_PASS |
(empty) | Admin dashboard password (required to enable admin API) |
In a managed directory deployment, the WBA server hosts JWKS directories on behalf of agents — agents don't need to host their own. The server domain is the canonical domain used for directory lookups and is assigned to agents when they're registered through the admin dashboard without specifying an explicit domain.
For example, if WBA_SERVER_DOMAIN=wba.example.com, an agent registered via the admin UI without a domain will have its directory served at:
https://wba.example.com/directories/{tenant}/{agent_id}/.well-known/http-message-signatures-directory
This contrasts with the decentralized model (per the IETF draft), where agents self-host directories at their own domain and register programmatically with proof-of-possession via POST /v1/agents/register. In that flow the agent provides its own domain.
| Method | Path | Description |
|---|---|---|
POST |
/v1/agents/register |
Register agent with public key + proof of possession |
POST |
/v1/agents/{id}/rotate |
Add new key (rotation) |
GET |
/v1/agents/{id} |
Get agent info and active keys |
DELETE |
/v1/agents/{id}/keys/{kid} |
Revoke a key |
GET |
/.well-known/http-message-signatures-directory |
JWKS directory (subdomain routing by domain) |
GET |
/directories/{tenant}/{id}/.well-known/http-message-signatures-directory |
JWKS directory (path routing, tenant-scoped) |
* |
/api/* |
Gateway-protected routes (echo backend) |
GET |
/health |
Health check |