Skip to content

Commit 82e3a85

Browse files
authored
Support reading the gateway key from the Authorization header (#649)
## Description As mentioned in https://mozilla-ai.github.io/any-llm/gateway/quickstart/, the `X-AnyLLM-Key` header must be provided to specify the master key when using the any-llm gateway: ``` -H "X-AnyLLM-Key: Bearer ${GATEWAY_MASTER_KEY}" \ ``` However it may be impractical to provide with existing generic OpenAI providers. So it would make sense to also support reading the key from the `Authorization` header. ## PR Type 🆕 New Feature ## Relevant issues ## Checklist - [x] I have added unit tests that prove my fix/feature works - [x] New and existing tests pass locally - [x] Documentation was updated where necessary - [x] I have read and followed the [contribution guidelines](https://github.com/mozilla-ai/any-llm/blob/main/CONTRIBUTING.md)```
1 parent a14c6e2 commit 82e3a85

File tree

3 files changed

+46
-7
lines changed

3 files changed

+46
-7
lines changed

docs/gateway/authentication.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,25 @@ any-llm-gateway offers two authentication methods, each designed for different u
88
| **Master Key** | Internal services, admin operations, trusted environments | Single key with full access | Requires manual user specification |
99
| **Virtual API Keys** | External apps, per-user access, customer-facing services | Multiple scoped keys | Automatic per-key tracking |
1010

11+
### Supported Headers
12+
13+
The gateway accepts authentication via two headers:
14+
15+
- **`X-AnyLLM-Key`** (preferred): The gateway's native authentication header
16+
- **`Authorization`**: Standard HTTP authorization header for OpenAI client compatibility
17+
18+
Both headers use the `Bearer <token>` format. When both headers are present, `X-AnyLLM-Key` takes precedence.
19+
20+
Using the `Authorization` header allows you to use the gateway with OpenAI-compatible clients without modification:
21+
22+
```python
23+
from openai import OpenAI
24+
25+
client = OpenAI(
26+
base_url="http://localhost:8000/v1",
27+
api_key="your-master-key-or-virtual-key", # Sent as Authorization: Bearer ...
28+
)
29+
```
1130

1231
## Master Key
1332
The master key is the root credential for your gateway installation. It has unrestricted access to all gateway operations and should be treated with the same security as your production database credentials.

src/any_llm/gateway/auth/dependencies.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,26 @@ def get_config() -> GatewayConfig:
2727

2828

2929
def _extract_bearer_token(request: Request, config: GatewayConfig) -> str:
30-
"""Extract and validate Bearer token from request header."""
31-
x_anyllm_key = request.headers.get(API_KEY_HEADER)
30+
"""Extract and validate Bearer token from request header.
3231
33-
if not x_anyllm_key:
32+
Checks X-AnyLLM-Key first, then falls back to standard Authorization header
33+
for OpenAI client compatibility.
34+
"""
35+
auth_header = request.headers.get(API_KEY_HEADER) or request.headers.get("Authorization")
36+
37+
if not auth_header:
3438
raise HTTPException(
3539
status_code=status.HTTP_401_UNAUTHORIZED,
36-
detail=f"Missing {API_KEY_HEADER} header",
40+
detail=f"Missing {API_KEY_HEADER} or Authorization header",
3741
)
3842

39-
if not x_anyllm_key.startswith("Bearer "):
43+
if not auth_header.startswith("Bearer "):
4044
raise HTTPException(
4145
status_code=status.HTTP_401_UNAUTHORIZED,
42-
detail=f"Invalid {API_KEY_HEADER} header format. Expected 'Bearer <token>'",
46+
detail="Invalid header format. Expected 'Bearer <token>'",
4347
)
4448

45-
return x_anyllm_key[7:]
49+
return auth_header[7:]
4650

4751

4852
def _verify_and_update_api_key(db: Session, token: str) -> APIKey:

tests/gateway/test_key_management.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,19 @@ def test_inactive_api_key_rejected(
244244
headers={API_KEY_HEADER: f"Bearer {api_key['key']}"},
245245
)
246246
assert response.status_code == 401
247+
248+
249+
def test_authorization_header_accepted(client: TestClient, test_config: GatewayConfig) -> None:
250+
"""Test that Authorization header works as fallback for OpenAI client compatibility."""
251+
# Use Authorization header instead of X-AnyLLM-Key
252+
auth_header = {"Authorization": f"Bearer {test_config.master_key}"}
253+
254+
response = client.post(
255+
"/v1/keys",
256+
json={"key_name": "auth-header-test"},
257+
headers=auth_header,
258+
)
259+
260+
assert response.status_code == 200
261+
data = response.json()
262+
assert data["key_name"] == "auth-header-test"

0 commit comments

Comments
 (0)