Authentication¶
WaveLedger uses three distinct auth schemes depending on the audience.
Session cookies (end users)¶
Set when the user logs in via POST /api/login (or self-onboards via POST /api/signup with a valid invite code). The cookie:
| Value | |
|---|---|
| Name | session |
| Format | URL-safe random 24-byte token (secrets.token_urlsafe) |
| HttpOnly | yes |
| SameSite | Lax |
| Lifetime | Persists across node restarts (SQLite-backed) |
Sessions are persisted to SQLite at {data_dir}/admin.db via api/admin_store.py — node restarts do not invalidate cookies, drop the pending queue, or void invite codes. End users stay logged in across deploys.
To attach the cookie from curl:
curl -c cookies.jar -X POST https://api.waveledger.net/api/login \
-H 'Content-Type: application/json' \
-d '{"name":"alice","token":"login-token-from-admin"}'
# Subsequent requests
curl -b cookies.jar https://api.waveledger.net/api/me
HTTP Basic (admin)¶
The admin endpoints (/api/admin/* and the /admin page) require HTTP Basic auth using credentials set via env vars on the node:
If WAVELEDGER_ADMIN_PASSWORD is unset, the password defaults to the literal string "change-me-via-env" — fine for local dev, never acceptable in production. The node logs a warning on startup if the default is in use.
Example:
The browser will prompt for credentials when you visit /admin. Most browsers cache them for the rest of the tab session.
Bearer API key (dashboard, node-local)¶
The dashboard runs on port 8080 of every node, bound to loopback (127.0.0.1) by default. It is not reachable from the public internet unless you change the bind host or front it with a reverse proxy + key check.
API key:
- Generated on first node boot (
auth.api_key_manager.APIKeyManager) - Stored at
{data_dir}/api_key.jsonwith mode0600 - Read by middleware on every dashboard write request
Read endpoints (GET /api/*) are public-by-default on the dashboard unless the node was started with --require-auth. Write endpoints (POST/PUT/DELETE) always require:
# Get your API key (run inside the container)
fly ssh console --config fly.chat.toml -C 'cat /data/api_key.json'
# Use it
KEY=$(fly ssh console --config fly.chat.toml -C 'cat /data/api_key.json' | jq -r .key)
curl -H "Authorization: Bearer $KEY" \
http://127.0.0.1:8080/api/mining
The dashboard's API key is local to that node — it's never broadcast to peers and changing it doesn't affect the chain.
No auth (public chain data)¶
The explorer endpoints (/api/explorer/*) are unauthenticated by design. Blockchain data is public; pretending otherwise is theater.
Likewise the signup + login endpoints take no auth (login itself is the auth-grant step).
CORS¶
All JSON responses include:
This is intentional — third-party explorers, monitoring dashboards, and SDKs need to be able to read chain data from any origin. No preflight handling is included; if you need OPTIONS-preflight support, front the node with a reverse proxy that adds it.
Future: signed-message login¶
The roadmap calls for wallet-signed-nonce login, where the user proves ownership of a private key by signing a server-issued nonce with their ML-DSA-87 keypair. That removes the need for server-side session storage entirely. Currently blocked on shipping browser-side ML-DSA via WASM.