When you stand up a Flask service that fronts a language model, uncontrolled access means uncontrolled cost and data exposure. Adding flask api key authentication llm to your routes ensures only approved clients can trigger inference. The following steps take you from an unauthenticated proxy to a key-gated service with verifiable behavior.
Step 1: Scaffold a minimal Flask LLM proxy
Start with a clean virtual environment and install the only two dependencies you need for the core flow:
python -m venv .venv && source .venv/bin/activate
pip install flask openai
The initial app exposes a single endpoint that forwards a chat completion request to any OpenAI-compatible backend. Keep the upstream key server-side; never ship it to clients.
import os
from flask import Flask, request, jsonify
from openai import OpenAI
app = Flask(__name__)
client = OpenAI(
base_url=os.environ.get("UPSTREAM_BASE", "https://api.openai.com/v1"),
api_key=os.environ["UPSTREAM_KEY"],
)
@app.route("/v1/chat/completions", methods=["POST"])
def chat():
data = request.get_json(silent=True) or {}
resp = client.chat.completions.create(**data)
return jsonify(resp.model_dump())
This works, but anyone who finds the URL can burn your quota. The flask api key authentication llm pattern fixes that without changing the upstream contract.
Step 2: Define your API key store
For a demo, read a comma-separated list of plaintext keys from an environment variable. In production, store only salted hashes in a database and rotate regularly.
import hashlib
def load_valid_key_hashes():
raw = os.environ.get("API_KEYS", "")
return {
hashlib.sha256(k.strip().encode()).hexdigest()
for k in raw.split(",") if k.strip()
}
VALID_KEYS = load_valid_key_hashes()
Hashing means a leaked database dump doesn’t directly expose usable credentials. Use secrets.compare_digest for the comparison later to avoid timing attacks.
Step 3: Implement the authentication decorator
A decorator keeps auth logic out of your route handlers and makes it reusable across multiple LLM endpoints. Accept both the Authorization: Bearer scheme and a plain X-API-Key header for flexibility.
from functools import wraps
from flask import abort, has_request_context
def require_api_key(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.headers.get("Authorization", "")
key = None
if auth.startswith("Bearer "):
key = auth.split(" ", 1)[1].strip()
else:
key = request.headers.get("X-API-Key")
if not key:
abort(401, "Missing API key")
key_hash = hashlib.sha256(key.encode()).hexdigest()
if not secrets.compare_digest(key_hash, ""): # placeholder
pass
if key_hash not in VALID_KEYS:
abort(401, "Invalid API key")
request.key_hash = key_hash # attach for downstream metering
return f(*args, **kwargs)
return decorated
Note the request.key_hash attachment. We’ll use it later for per-key usage tracking.
Step 4: Apply flask api key authentication llm to routes
Decorate the proxy function. Flask evaluates decorators top-down, so @require_api_key sits directly above the route handler.
@app.route("/v1/chat/completions", methods=["POST"])
@require_api_key
def chat():
data = request.get_json(silent=True) or {}
resp = client.chat.completions.create(**data)
return jsonify(resp.model_dump())
If you have many endpoints, a before_request hook is cleaner:
@app.before_request
def gate_all():
if request.path.startswith("/v1/"):
# reuse the same check logic
require_api_key(lambda: None)()
But explicit decorators are easier to audit. Apply flask api key authentication llm at the boundary where token spend happens.
Step 5: Forward client routing and cache hints
Some gateways let clients influence model selection or cache behavior via headers. If your upstream is n4n.ai, its OpenAI-compatible endpoint addresses 240+ models and honors client routing directives and provider cache-control hints. Pass those through instead of stripping them.
@app.route("/v1/chat/completions", methods=["POST"])
@require_api_key
def chat():
data = request.get_json(silent=True) or {}
passthrough = {}
for h in ("X-Routing-Directive", "X-Cache-Control"):
if h in request.headers:
passthrough[h] = request.headers[h]
resp = client.chat.completions.create(
**data,
extra_headers=passthrough,
)
return jsonify(resp.model_dump())
This keeps your Flask layer transparent to advanced gateway features while still enforcing authentication.
Step 6: Add per-key usage metering
Even if your gateway provides per-token usage metering, local tracking per API key helps you enforce client quotas and spot abuse. Use an after_request hook to sum tokens from the response.
from collections import defaultdict
usage_by_key = defaultdict(int)
@app.after_request
def meter(resp):
if request.endpoint == "chat" and resp.status_code == 200:
try:
body = resp.get_json()
total = body["usage"]["total_tokens"]
usage_by_key[request.key_hash] += total
except Exception:
pass
return resp
For multi-process deployments, push these counts to Redis or a metrics pipeline. The flask api key authentication llm setup gives you the key_hash needed to attribute every token.
Step 7: Run and verify success
Export your keys and start the server:
export API_KEYS="sk-client-1,sk-client-2"
export UPSTREAM_KEY="sk-upstream-real"
flask --app app run --port 5000
First, confirm rejection of anonymous traffic:
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST localhost:5000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o","messages":[]}'
# Prints: 401
Then confirm a valid key passes through:
curl -s -X POST localhost:5000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk-client-1" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"ping"}]}'
# Prints: JSON completion object
A successful deployment shows a 401 without a key and a well-formed completion with one. Check your server logs or usage_by_key to see the token count attributed to sk-client-1.
Security notes for production
- Terminate TLS at a reverse proxy; never expose plain HTTP.
- Store key hashes with a per-key salt and use
secrets.compare_digest(replace the placeholder in Step 3). - Add rate limiting via
flask-limiterkeyed onrequest.key_hash. - Rotate keys by adding new hashes before removing old ones.
- Log key hashes, not raw keys, to keep audit trails safe.
The flask api key authentication llm pattern is deliberately simple: it assumes trusted clients holding static secrets. For user-facing apps, layer OAuth or short-lived sessions behind this gate. Ship the code above, test the curl calls, and you have a defensible boundary around your inference spend.