3 · Token-based auth, end to end

Generate a token, wire it into config, and confirm 401 → 200 from curl, the OpenAI SDK, and the dashboard.

What it is

Bearer-token auth guarding /v1/*, /stats, and /metrics. Requests without a valid Authorization: Bearer <token> get 401 once auth.tokens_env is set; /health stays open regardless (so load balancers never need a token).

Why

Any time routsi is reachable beyond localhost — a shared dev box, a team's LAN, a cloud VM — you want a cheap, low-ceremony gate before anyone can spend your upstream API budget or read /metrics. Tokens compare in constant time and are never logged or printed; only the env-var name lives in config.

Configure

1 · generate + store
routsi token -n 1
# → prints: rtk_9f3a...

echo 'ROUTSI_TOKENS=rtk_9f3a...' >> ~/.config/routsi/.env
chmod 600 ~/.config/routsi/.env
2 · models.yaml
auth:
  tokens_env: ROUTSI_TOKENS
3 · curl without the header
curl -i localhost:8080/v1/chat/completions \
  -d '{"model":"auto","messages":[{"role":"user","content":"hi"}]}'
# → HTTP/1.1 401 Unauthorized
4 · curl with the header
curl -i localhost:8080/v1/chat/completions \
  -H 'Authorization: Bearer rtk_9f3a...' \
  -d '{"model":"auto","messages":[{"role":"user","content":"hi"}]}'
# → HTTP/1.1 200 OK
5 · same thing via the OpenAI Python SDK
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8080/v1", api_key="rtk_9f3a...")
r = client.chat.completions.create(model="auto", messages=[{"role":"user","content":"hi"}])
print(r.choices[0].message.content)
# → 200, works unmodified — the SDK sends the token as a normal api_key
6 · the dashboard, token in the URL
open "http://localhost:8080/?token=rtk_9f3a..."
/health stays open regardless of auth config — health checks and load balancers never need a token.

How to adapt

Rotate by generating a new token with routsi token -n 1 and appending it to ROUTSI_TOKENS (comma-separated — old and new both work until you remove the old one). To require client certificates instead of or alongside tokens, layer in mTLS under the same models.yaml. Never put a token value in models.yaml itself — only the env-var name (tokens_env) belongs there.

Next: mTLS →