Tokens & API keys¶
Three token classes, three storage forms.
User JWT¶
Standard login token after successful login + 2FA.
| Field | Value |
|---|---|
| Format | RS256 signed JWT |
| Claims | sub (user ID), tenant_id, tenant_scope, permissions[], exp |
| Lifetime | default 24 h, refreshable |
| Secret | SECRET_KEY (RSA private key, default HMAC symmetric) |
| Storage | browser (HttpOnly cookie + localStorage), mobile app AsyncStorage |
Refresh¶
Before expiry a new token is fetched automatically — as long as the refresh token is valid (default 7 days). Refresh token is invalidated on logout.
Invalidation¶
There's no server-side JWT blocklist (too costly). On logout: frontend discards the token. On account block: users.active = false causes resource lookups for the user to fail in future requests — token effectively expires next request.
JWT in browser is as secure as the browser
XSS protection is mandatory: strict CSP header, no user content as HTML without escape, wiki Markdown is sanitized. All on the frontend.
Agent token¶
For agents on monitored machines.
| Field | Value |
|---|---|
| Format | vesana_agent_<32 url-safe base64> |
| Server storage | SHA256 hash in agent_tokens.token_hash |
| Agent storage | plaintext in /etc/vesana-agent/config.yaml (chmod 600) |
| Lifetime | unlimited |
| Binding | 1:1 to a host |
| Auth header | X-Agent-Token: vesana_agent_xxxx |
Generation¶
UI only on the host detail. Plaintext shown exactly once. Backend stores only the hash.
Rotation¶
Host detail → Revoke token → new token. On the machine the config must be updated, otherwise the agent gets 401.
Or: re-install via one-command installer with new token.
Collector API key¶
For collectors in the customer network.
| Field | Value |
|---|---|
| Format | vesana_<custom-prefix>_<32 bytes> |
| Server storage | SHA256 hash in api_keys.key_hash |
| Collector storage | plaintext in /etc/vesana/collector.env |
| Auth header | X-API-Key: vesana_xxxxx |
| Binding | 1:1 to a collector |
key_prefix hasn't been written for new keys since v0.x (empty string). Receiver lookup is via key_hash only.
Personal access tokens¶
Through /profile → API keys, users can also generate personal API keys — for CLI scripts, integrations.
| Field | Value |
|---|---|
| Scope | read-only / read-write |
| Expiry | required (max 1 year) |
| Tenant scope | as the user |
Use: Authorization: Bearer <pat>.
Rotation: revoke old → create new. Recommendation: rotate at least yearly.
Hashing¶
All tokens are SHA256 hashed in DB. Benefit:
- DB leak doesn't reveal a working token
- Comparison with hashed values is O(1) on index
We don't salt — tokens are random enough (32 bytes entropy >> brute-forceable).
TLS required¶
Tokens are accepted only via HTTPS. HTTP requests get redirect (frontend) or 426 Upgrade Required (API). Self-signed certs are OK for tests, in production: Let's Encrypt or own cert.
Audit¶
Token operations (create / revoke) appear in audit log with action = agent_token.create / .revoke / api_key.create / .revoke.
Theft detection¶
| Symptom | Possible cause |
|---|---|
| Agent token reporting from two IPs | token compromised or cloned |
| API key suddenly used for unusual endpoints | PAT possibly pushed to repo |
| User JWT active on two devices | normal — no problem |
For agent and collector tokens: multi-IP heuristic in the UI (column „Last IP") helps spot anomalies.