Alpha

Authentication

Secure your Cntrl API with API keys and IP whitelisting.

Security Warning

Authentication alone may not make Cntrl safe for the public internet. We strongly recommend against exposing port 9990 via router port forwarding. Use a VPN (Tailscale/WireGuard) for remote access.

By default, Cntrl's API is open to anyone on your network. If you are on a shared network (like a dorm or office) or using a VPN like Tailscale, you should enable authentication to secure endpoints.

How it works

Whitelisted IPs bypass the API key check entirely. This means trusted devices (like a dashboard server) can use EventSource or WebSocket without custom headers, while external clients still need the API key.

Quick Setup

Add this to your config.json:

{
  "auth": {
    "enabled": true,
    "api_key": "your-secret-key-here",
    "allowed_ips": ["192.168.1.100"],
    "blocked_ips": []
  }
}

The IP 192.168.1.100 (your dashboard server, for example) will bypass the API key check entirely.

IP Blocking (Blacklist)

You can block specific IPs from accessing your API entirely. Blocked IPs are checked first, even when authentication is disabled.

{
  "auth": {
    "enabled": false,
    "blocked_ips": ["192.168.1.50", "10.0.0.0/8"]
  }
}

Blocked IPs Take Priority

If an IP appears in both allowed_ips and blocked_ips, it will be blocked. The blocklist is always checked first.

Making Authenticated Requests

Include your API key in the Authorization header:

curl -H "Authorization: Bearer your-secret-key-here" \
     http://your-pc:9990/api/system

Public Endpoint

The /api/status endpoint is always public for health checks—no authentication required.

IP Whitelisting

For additional security, you can restrict access to specific IP addresses:

{
  "auth": {
    "enabled": true,
    "api_key": "your-secret-key-here",
    "allowed_ips": ["192.168.1.100", "192.168.1.0/24"]
  }
}

Supported Formats

FormatExampleDescription
Single IP192.168.1.100Exact IP match
CIDR192.168.1.0/24Subnet range
Wildcard*Allow all IPs (only API key checked)

Common Configurations

Tailscale Network

If both your PC and dashboard are on Tailscale:

{
  "auth": {
    "enabled": true,
    "api_key": "your-secret-key-here",
    "allowed_ips": ["100.64.0.0/10"]
  }
}

Local Network Only

Restrict to your home network:

{
  "auth": {
    "enabled": true,
    "api_key": "your-secret-key-here",
    "allowed_ips": ["192.168.1.0/24"]
  }
}

API Key Only (No IP Restriction)

Allow any IP with the correct key:

{
  "auth": {
    "enabled": true,
    "api_key": "your-secret-key-here",
    "allowed_ips": ["*"]
  }
}

Dashboard Integration

JavaScript / TypeScript

const API_KEY = "your-secret-key-here";
const API_URL = "http://your-pc:9990";

// Regular fetch
const response = await fetch(`${API_URL}/api/system`, {
  headers: {
    Authorization: `Bearer ${API_KEY}`,
  },
});

// SSE Stream
const eventSource = new EventSource(
  `${API_URL}/api/stream?fields=cpu,memory`,
  // Note: EventSource doesn't support custom headers natively
  // See workaround below
);

SSE and Authentication

The browser's EventSource API doesn't support custom headers. For authenticated SSE streams, you have two options:

  1. Use IP whitelisting - Add your dashboard's IP to allowed_ips and skip the API key check for SSE
  2. Use a fetch-based SSE client like eventsource-parser

WebSocket Authentication

The WebSocket endpoint (/api/ws) follows the same authentication rules. Use IP whitelisting for browser WebSocket connections, or pass the API key via a query parameter if needed.

Fetch-based SSE Example

async function streamWithAuth(url, apiKey, onMessage) {
  const response = await fetch(url, {
    headers: { Authorization: `Bearer ${apiKey}` },
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const text = decoder.decode(value);
    const lines = text.split("\n");

    for (const line of lines) {
      if (line.startsWith("data: ")) {
        onMessage(JSON.parse(line.slice(6)));
      }
    }
  }
}

// Usage
streamWithAuth(
  "http://your-pc:9990/api/stream?fields=cpu,memory",
  "your-secret-key-here",
  (data) => console.log(data),
);

Error Responses

StatusMeaning
401 UnauthorizedMissing or invalid API key
403 ForbiddenIP is blocked, or IP not in whitelist (when enabled)

No Error Body

For security reasons, 401 and 403 responses do not include a JSON body with details.

Generating a Secure Key

Use a random string generator to create a strong API key:

# Linux/macOS
openssl rand -base64 32

# PowerShell
[Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Maximum 256 }))

Key Length

A 32-character random string is more than enough for a home network. For production, consider using a UUID or a 64-character hex string.

On this page