API keys (chm_ Bearer)
Issue and use chm_ Bearer tokens for programmatic access — MCP, scripts, CI, and the chm CLI (`GET /api/v1/auth/cli` discovery).
The API key layer is provider-agnostic. It activates whenever CHM_API_KEY_SECRET is set, regardless of which auth provider is configured. This is how MCP clients, scripts, and CI pipelines authenticate.
- Keys are HMAC-SHA-256 signed and time-limited.
- A valid key satisfies the auth guard on any
/api/v1/*route. - The key layer coexists with any provider: a valid key always grants access, and if the key is absent or invalid the provider's check runs next.
Setup
Set the signing secret server-side:
CHM_API_KEY_SECRET=a-long-random-string-keep-this-secretKeep the secret server-side only
Never put CHM_API_KEY_SECRET in a VITE_* or NEXT_PUBLIC_* variable — these are inlined into the client bundle at build time. Set it as a server-side env var or Worker secret only.
Set this out-of-band in production. For Cloudflare Workers:
wrangler secret put CHM_API_KEY_SECRETIssue a token
Call the key-issuance endpoint. It uses its own secret-based auth (the same CHM_API_KEY_SECRET):
curl -X POST https://dash.example.com/api/v1/auth/api-key \
-H "Authorization: Bearer $CHM_API_KEY_SECRET"The response includes a chm_ token. Tokens are time-limited; re-issue before expiry.
Verify
Pass the token in the Authorization header on any /api/v1/* route:
curl https://dash.example.com/api/v1/hosts \
-H "Authorization: Bearer chm_eyJ..."curl https://dash.example.com/api/mcp \
-H "Authorization: Bearer chm_eyJ..." \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'Device login (chm auth login)
chm auth login auto-detects the dashboard auth mode via
GET /api/v1/auth/cli (public; no auth_mode in chm.toml):
Discovery method | Meaning | CLI action |
|---|---|---|
none | Open API (auth=none, no CHM_API_KEY_SECRET) | No credentials needed |
device | Device login enabled | RFC 8628 browser flow |
api_key | Key required, device off | Prompt / --api-key / CHM_API_KEY |
Device login itself is controlled by CHM_DEVICE_LOGIN:
| Value | Behaviour |
|---|---|
auto (default) | Cloud: on when CHM_API_KEY_SECRET is set (approve requires a signed-in session at /device). Self-hosted / OSS: off — typical internal networks mint a key once or leave the API open. |
true | Force on. With CHM_AUTH_PROVIDER=none this is device-only approve (no Clerk): anyone who can open /device can mint a token bound to CHM_DEVICE_LOGIN_SUBJECT (default self-hosted). Codes persist in D1 when bound, otherwise in memory (single-node). |
false | Force off everywhere (/api/v1/auth/device/* → 503). |
# Self-hosted opt-in on a trusted LAN
CHM_AUTH_PROVIDER=none
CHM_API_KEY_SECRET=a-long-random-string
CHM_DEVICE_LOGIN=trueDiscovery: GET /api/v1/auth/cli returns { method, api, authProvider, deviceLogin, hint }.
Legacy: GET /api/v1/auth/device/status returns { enabled, mode, deviceOnly, reason, store }.