Skip to content

API Security

aranet-service includes optional API-key authentication plus enabled-by-default rate limiting and localhost-only CORS defaults.

FeatureDefaultNotes
API key authenticationDisabledEnable with security.api_key_enabled = true
Rate limitingEnabledDefaults to 100 requests per 60 seconds per IP
Rate-limit IP cache10000 entriesControlled by security.rate_limit_max_entries
CORShttp://localhost, http://127.0.0.1Override with security.cors_origins
Device ID sanitizationAlways enabledUsed for API-facing identifiers and dashboard output

Add a [security] section to ~/.config/aranet/server.toml:

[security]
api_key_enabled = true
api_key = "replace-with-a-random-string-at-least-32-characters"
rate_limit_enabled = true
rate_limit_requests = 100
rate_limit_window_secs = 60
rate_limit_max_entries = 10000
cors_origins = [
"http://localhost",
"http://127.0.0.1",
"https://dashboard.example.com",
]

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.

Terminal window
export ARANET_API_KEY="replace-with-your-api-key"
curl -H "X-API-Key: $ARANET_API_KEY" http://localhost:8080/api/devices
curl -H "X-API-Key: $ARANET_API_KEY" http://localhost:8080/metrics

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": "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"
}
  • 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.
Terminal window
openssl rand -base64 32

Rate limiting is enabled by default, even when API key auth is off.

[security]
rate_limit_enabled = true
rate_limit_requests = 100
rate_limit_window_secs = 60
rate_limit_max_entries = 10000

When a client exceeds the limit, the service returns 429 Too Many Requests with these headers:

HeaderDescription
X-RateLimit-LimitMaximum requests per window
X-RateLimit-RemainingRemaining requests in the current window
Retry-AfterSeconds 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 = ["*"]
  1. Generate a random API key and store it securely.
  2. Enable api_key_enabled = true.
  3. Keep rate_limit_enabled = true unless you are terminating traffic behind a trusted internal proxy.
  4. Restrict cors_origins to the exact frontends that need browser access.
  5. Bind the service to 127.0.0.1:8080 and publish it through HTTPS at the reverse proxy.
[server]
bind = "127.0.0.1:8080"
[security]
api_key_enabled = true
api_key = "replace-with-a-random-string-at-least-32-characters"
rate_limit_enabled = true
rate_limit_requests = 100
rate_limit_window_secs = 60
rate_limit_max_entries = 10000
cors_origins = ["https://dashboard.example.com"]