Sits behind a reverse proxy (ex: Traefik) and checks the provided token in the authorization header - Bearer token.
Tiny FastAPI ForwardAuth validator that accepts a SHA-256 hash of user:token as an API key.
Admins keep a simple user:token config file; clients compute sha256("user:token") and present that hex digest in the Authorization: Bearer <hash> header. Designed to run behind Traefik (ForwardAuth) and protect services like Ollama.
- Precomputes
sha256(user:token)at startup for fast verification lookups. - Exposes:
POST/GET /verify— Traefik ForwardAuth endpoint (accepts common HTTP methods).GET /health— simple health check.POST /reload-config— optional config reload (protected byRELOAD_SECRETif set).POST /hash— generate SHA-256 hash foruser:token(protected byRELOAD_SECRET).
- Runs as a Docker container (Python 3.12, FastAPI + Uvicorn).
- Config format is human-friendly:
user:token(one per line).
.
├─ main.py
├─ requirements.txt
├─ Dockerfile-slim
├─ Dockerfile-alpine
├─ docker-compose.yml
├─ ...
└─ config/
└─ users.cfg # example config file
- Admin writes
user:tokenintoconfig/users.cfg. - On startup (and on reload), the server loads the file and precomputes a mapping:
sha256(user:token) -> user - Client computes the same SHA-256 hex digest and calls:
Authorization: Bearer <hex-digest> - Server checks digest in O(1) (dict lookup). If found, returns
200 OKand headerX-Forwarded-User: <user>for the upstream service.
import hashlib
user = "alice"
token = "ZGVtbzEyMw==" # token stored in config
digest = hashlib.sha256(f"{user}:{token}".encode("utf-8")).hexdigest()
print(digest)USER="your_name"
TOKEN=$(openssl rand -base64 24)
HASH=$(printf "%s:%s" "$USER" "$TOKEN" | openssl dgst -sha256 -hex | awk '{print $2}')
echo $HASH
echo $TOKENThe HASH should then presented to the verify endpoint, while the TOKEN is placed safely into the users.cfg.
HASH contains the computed hex digest.
curl -i -H "Authorization: Bearer $HASH" http://localhost:8000/verifyExpected response (headers show X-Forwarded-User):
HTTP/1.1 200 OK
X-Forwarded-User: alice
...
Expected:
HTTP/1.1 401 Unauthorized
...
curl http://localhost:8000/health
# returns "Simple Auth Server is running"# If RELOAD_SECRET=supersecret was set in the container:
curl "http://localhost:8000/reload-config?secret=supersecret"
# returns {"loaded_users": N}The secret should be set as environment variable your container or shell before running the server:
export RELOAD_SECRET="supersecret" # Linux/macOS
set RELOAD_SECRET=supersecret # Windows cmd
$env:RELOAD_SECRET="supersecret" # PowerShell
- RELOAD_SECRET=supersecret # Docker Composecurl -X POST "http://localhost:8000/hash" \
-H "Content-Type: application/json" \
-d '{"user": "alice", "token": "ZGVtbzEyMw==", "secret": "supersecret"}'
# returns {"username": "alice", "hash": "<sha256-hex>"}Security note: The /hash endpoint is only meant as a helper for admins. Do not expose it publicly. Clients should compute hashes themselves.
A convenience endpoint to compute the SHA-256 hex digest of user:token.
- Method:
POST - Path:
/hash - Content-Type:
application/json - Body:
{ "user": "<username>", "token": "<token>", "secret": "<RELOAD_SECRET>" }
{"username": "<user>", "hash": "<sha256-hex>"}Notes: This is for testing/bootstrapping; keep it on an internal network and protect it with RELOAD_SECRET. The GET method is deprecated and kept for backward compatibility only.
Deprecated GET method:
curl "http://localhost:8000/hash?user=alice&token=ZGVtbzEyMw==&secret=supersecret"
# returns {"username": "alice", "hash": "<sha256-hex>"}Warning: Tokens in query parameters leak into server and proxy logs. Use POST instead.
Use this snippet on the service you want to protect (example: Ollama). Traefik will call the ForwardAuth endpoint and only forward the request if the auth service returns 200. See below.
Notes:
- Traefik must be on the same Docker network as the
forwardauthservice and able to resolve the service name (hereforwardauth). - Traefik forwards request headers to the
forwardauthendpoint (includingAuthorization) so the service can validate them.
CONFIG_FILE— path to config file inside container (default:/config/users.cfg).RELOAD_SECRET— optional secret to protectPOST /reload-configandGET /hash. If set, you must provide the secret when calling these endpoints.
Mount your config directory read-only into the container:
volumes:
- ./config:/config:roA minimal docker-compose.yml (example):
services:
forwardauth:
build: .
container_name: forwardauth
restart: unless-stopped
volumes:
- ./config:/config:ro
environment:
- CONFIG_FILE=/config/users.cfg
# - RELOAD_SECRET=some-secret
labels:
- "traefik.enable=false"
# example protected service (Ollama)
ollama:
image: ghcr.io/ollama/ollama:latest
container_name: ollama
labels:
- "traefik.enable=true"
- "traefik.http.routers.ollama.rule=Host(`ai.example.com`)- Clone the repo:
git clone https://github.com/Perhan35/simple-auth-verifier.git
cd simple-auth-verifier- Create a config directory and example
users.cfg:
# config/users.cfg
# Format: user:token
alice:ZGVtbzEyMw==
bob:U29tZVBhc3N3b3Jk
- Build and run with Docker Compose:
docker compose up --build -dOR
- Activate Pyenv to work locally
pyenv install -s 3.12.0 && \
pyenv local 3.12.0 && \
python -m venv venv && \
source venv/Scripts/activate && \
pip install --upgrade pip && \
pip install -r requirements.txt- Run it!
RELOAD_SECRET="supersecret" uvicorn main:APP --reloadThe auth service listens on container port 8000. By default you can test it on the host at http://localhost:8000 when running locally.
- Treat
config/users.cfgas a secret. Use read-only mounts, OS file permissions, Docker secrets, or a secrets manager for production. - Do not expose the auth service publicly. Keep it on the internal network and let Traefik call it. Set the service label
traefik.enable=false. - Use TLS for client → Traefik and Traefik → upstream. Traefik should terminate TLS at the edge. Internal HTTP between Traefik and forwardauth is acceptable if the network is trusted.
- Rotate tokens periodically by changing the
users.cfgfile and calling/reload-config(or restarting). - Scale horizontally by running multiple instances behind Traefik if you need higher throughput. Keep config synchronized (e.g., orchestrator volume, config management, or a shared secret store).
- Optional hardening: store hashed tokens instead of raw tokens in config; use HSM/vault for token storage if required by policy.
- Hashes are precomputed at load time and stored in an in-memory dict map (
hash -> user) for O(1) lookups. This is efficient for hundreds to thousands of users. - For massive user lists or strict timing-attack protection across all entries, consider alternate approaches (e.g., HSM, rate-limiting, or per-request HMACs).
401responses:- Verify client computed hash using exact
user:tokenstring and SHA-256 hex digest. - Check that Traefik forwards the
Authorizationheader to the auth service (no header stripping middleware).
- Verify client computed hash using exact
Config not foundwarnings:- Ensure
CONFIG_FILEpath matches the mounted file inside the container and volume is mounted correctly.
- Ensure
- Networking issues:
- Ensure Traefik and
forwardauthare on the same Docker network. Check service name resolution.
- Ensure Traefik and