Auth Internals
Snippbot uses API key authentication for all protected endpoints. Keys are stored as SHA-256 hashes and validated on every request via middleware.
Authentication model
Section titled “Authentication model”There is no user login or session management in the self-hosted tier. Authentication is purely API key based:
- On first run (bootstrap mode): no key required — the setup wizard creates the first key
- After setup: all API requests must include a valid API key
- The UI stores its key in the browser’s
localStorageand sends it with every request
Key format
Section titled “Key format”Generated keys use the format:
snip_<random_hex>Example: snip_f7a3c2d1e8b4a6f9c2d1e8b4a6f9c2d1e8b4a6f9c2d1e8b4a6f9c2d1
Keys are never stored in plaintext. The storage layer saves only:
SHA-256(key)— for validation- Key name and description
- Creation and last-used timestamps
- Permissions scope
Request authentication
Section titled “Request authentication”Include the API key as a Bearer token in the Authorization header:
GET /api/agents HTTP/1.1Authorization: Bearer snip_f7a3c2d1e8b4a6f9...The middleware computes SHA-256(provided_key) and compares it to the stored hash using a constant-time comparison to prevent timing attacks.
Middleware stack
Section titled “Middleware stack”Requests flow through this middleware chain:
Request ↓SecurityHeadersMiddleware — X-Content-Type-Options, X-Frame-Options, etc. ↓CORSMiddleware — Origin validation and preflight handling ↓AuthMiddleware — API key validation ↓Route handlerSecurityHeadersMiddleware
Section titled “SecurityHeadersMiddleware”Adds security headers to all responses:
X-Content-Type-Options: nosniffX-Frame-Options: DENYX-XSS-Protection: 1; mode=blockReferrer-Policy: strict-origin-when-cross-originCORSMiddleware
Section titled “CORSMiddleware”Default CORS policy:
- Allowed origins:
http://localhost:5173(UI dev server) + the daemon’s own origin - Allowed methods:
GET, POST, PUT, PATCH, DELETE, OPTIONS - Allowed headers:
Authorization, X-API-Key, Content-Type - Max age: 3600 seconds
Configure with SNIPPBOT_CORS_ORIGINS to add additional allowed origins.
AuthMiddleware
Section titled “AuthMiddleware”Validates the API key on every protected request. Unauthenticated requests return:
HTTP/1.1 401 Unauthorized{"error": "Missing Authorization header"}Or if a key is supplied but invalid:
HTTP/1.1 401 Unauthorized{"error": "Invalid or revoked API key"}Public endpoints (no auth)
Section titled “Public endpoints (no auth)”Some endpoints are accessible without authentication:
| Endpoint | Purpose |
|---|---|
GET /health | Health check for load balancers |
GET /health/live | Liveness probe (Kubernetes) |
GET /health/ready | Readiness probe (Kubernetes) |
GET /api/ | API index / discovery |
GET /api/metrics/prometheus | Prometheus metrics scrape |
POST /api/auth/login | Web UI login |
POST /api/auth/validate | API key validation (pass key in header) |
GET /api/auth/bootstrap | Bootstrap state check |
POST /api/setup/* | Setup wizard (before any key is created) |
POST /api/auth/device/pair | Device pairing initiation |
Bootstrap mode
Section titled “Bootstrap mode”On first run, before any API key exists, the daemon enters bootstrap mode:
- The setup wizard (
snippbot setupor UI/setuppage) is accessible without auth - All other endpoints return 401
- Bootstrap mode auto-disables after the first API key is created
Bootstrap mode can be detected:
GET /api/health{"status": "ok", "bootstrap_mode": true}API key management
Section titled “API key management”Creating keys
Section titled “Creating keys”Via setup wizard (first key) or via the API:
POST /api/auth/keysAuthorization: Bearer existing_key
{"name": "CI/CD Pipeline", "description": "GitHub Actions integration"}Response:
{ "id": "key_abc123", "name": "CI/CD Pipeline", "key": "snip_f7a3c2d1...", ← Only shown once! "created_at": "2026-03-02T00:00:00Z"}Rotating keys
Section titled “Rotating keys”POST /api/auth/keys/{id}/rotateAuthorization: Bearer existing_keyThe old key is immediately invalidated. A new key is returned.
Revoking keys
Section titled “Revoking keys”DELETE /api/auth/keys/{id}Authorization: Bearer existing_keyReturns {"revoked": true}.
Device authentication
Section titled “Device authentication”Device agents (remote machines) use a separate token-based auth system:
- Pairing: daemon issues a 6-digit one-time code (valid 5 min)
- Device sends code + device metadata → receives a
device_token - WebSocket connections:
ws://daemon:18781/api/devices/ws?token=DEVICE_TOKEN - Token validation: SHA-256 hash lookup on every WebSocket connect
Device tokens are stored separately in devices.db and are not valid for API endpoints.
Channel adapter authentication
Section titled “Channel adapter authentication”Each platform adapter verifies inbound webhooks using platform-specific mechanisms:
| Platform | Verification method |
|---|---|
| Discord | Ed25519 signature (X-Signature-Ed25519) |
| Slack | HMAC-SHA256 (X-Slack-Signature) |
| Telegram | Secret token header (X-Telegram-Bot-Api-Secret-Token) |
HMAC-SHA256 (X-Hub-Signature-256) | |
| Teams | Bot Framework OAuth2 JWT validation |
| Google Chat | JWT signed by Google service account public key |
These are separate from the API key system — channel webhook endpoints don’t require an API key.
Production checklist
Section titled “Production checklist”- Change the default API key after setup
- Store keys in environment variables, not in code
- Set
SNIPPBOT_CORS_ORIGINSto your actual domain (remove localhost) - Enable TLS via reverse proxy — never run HTTP in production
- Set
SNIPPBOT_API_KEY_REQUIRED=trueto disable anonymous access - Rotate keys on a regular schedule
- Monitor key last-used timestamps for unused keys