API Security
aranet-service includes optional API-key authentication plus enabled-by-default rate limiting and localhost-only CORS defaults.
Security Defaults
Section titled “Security Defaults”| Feature | Default | Notes |
|---|---|---|
| API key authentication | Disabled | Enable with security.api_key_enabled = true |
| Rate limiting | Enabled | Defaults to 100 requests per 60 seconds per IP |
| Rate-limit IP cache | 10000 entries | Controlled by security.rate_limit_max_entries |
| CORS | http://localhost, http://127.0.0.1 | Override with security.cors_origins |
| Device ID sanitization | Always enabled | Used for API-facing identifiers and dashboard output |
Configuration
Section titled “Configuration”Add a [security] section to ~/.config/aranet/server.toml:
[security]api_key_enabled = trueapi_key = "replace-with-a-random-string-at-least-32-characters"
rate_limit_enabled = truerate_limit_requests = 100rate_limit_window_secs = 60rate_limit_max_entries = 10000
cors_origins = [ "http://localhost", "http://127.0.0.1", "https://dashboard.example.com",]Authentication
Section titled “Authentication”When API key authentication is enabled, protected requests must include X-API-Key.
Public routes:
//dashboard/api/health
Protected routes include the REST API, /metrics, and /api/ws.
REST and Metrics Requests
Section titled “REST and Metrics Requests”export ARANET_API_KEY="replace-with-your-api-key"
curl -H "X-API-Key: $ARANET_API_KEY" http://localhost:8080/api/devicescurl -H "X-API-Key: $ARANET_API_KEY" http://localhost:8080/metricsWebSocket Requests
Section titled “WebSocket Requests”Non-browser clients can still use X-API-Key. Browser clients can use the token query parameter, but only on /api/ws:
const ws = new WebSocket('ws://localhost:8080/api/ws?token=your-api-key');Error Response
Section titled “Error Response”{ "error": "Invalid or missing API key", "hint": "Provide a valid API key in the X-API-Key header, or use the 'token' query parameter only for /api/ws"}API Key Requirements
Section titled “API Key Requirements”- Use a cryptographically random value.
- The key must be at least 32 characters.
- Store it in the config file with restrictive permissions or inject it via deployment tooling.
openssl rand -base64 32Rate Limiting
Section titled “Rate Limiting”Rate limiting is enabled by default, even when API key auth is off.
[security]rate_limit_enabled = truerate_limit_requests = 100rate_limit_window_secs = 60rate_limit_max_entries = 10000When a client exceeds the limit, the service returns 429 Too Many Requests with these headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per window |
X-RateLimit-Remaining | Remaining requests in the current window |
Retry-After | Seconds until the next window |
Example response body:
{ "error": "Too many requests", "retry_after": 45}By default, only localhost origins are allowed:
[security]cors_origins = ["http://localhost", "http://127.0.0.1"]To allow a deployed web frontend:
[security]cors_origins = ["https://dashboard.example.com"]To allow every origin:
[security]cors_origins = ["*"]Production Checklist
Section titled “Production Checklist”- Generate a random API key and store it securely.
- Enable
api_key_enabled = true. - Keep
rate_limit_enabled = trueunless you are terminating traffic behind a trusted internal proxy. - Restrict
cors_originsto the exact frontends that need browser access. - Bind the service to
127.0.0.1:8080and publish it through HTTPS at the reverse proxy.
Example Production Config
Section titled “Example Production Config”[server]bind = "127.0.0.1:8080"
[security]api_key_enabled = trueapi_key = "replace-with-a-random-string-at-least-32-characters"rate_limit_enabled = truerate_limit_requests = 100rate_limit_window_secs = 60rate_limit_max_entries = 10000cors_origins = ["https://dashboard.example.com"]