fix(anthropic): properly handle OAuth tokens (sk-ant-oat*) across all…#19802
fix(anthropic): properly handle OAuth tokens (sk-ant-oat*) across all…#19802tompipe wants to merge 3 commits intoBerriAI:mainfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
2a4547f to
0e51785
Compare
… endpoints OAuth tokens require Authorization header (not x-api-key) plus specific beta headers. This change: - Add `is_anthropic_oauth_key()` to detect OAuth token prefix - Add `set_anthropic_headers()` to set correct auth header based on key type - Update `optionally_handle_anthropic_oauth()` to handle tokens from both Authorization header and direct api_key parameter - Apply OAuth handling consistently across all Anthropic endpoints: batches, completion, count_tokens, files, skills, and pass-through - Add unit tests and manual integration test suite to manually verify with a valid oauth key Fixes #19618
0e51785 to
cf78008
Compare
The /anthropic/* pass-through endpoint was hardcoding x-api-key header, ignoring OAuth tokens from incoming requests. Now it: - Checks incoming Authorization header for OAuth tokens (sk-ant-oat*) - Uses Authorization header format for OAuth tokens - Falls back to x-api-key with stored credentials for regular requests - Includes required anthropic-dangerous-direct-browser-access header Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
clean_headers function should be modified too, when you want to use the router for your anthropic llm calls (non-passthrough) - the code flow goes through clean_headers which should be modified to pass the auth header through
def clean_headers(
headers: Headers, litellm_key_header_name: Optional[str] = None
) -> dict:
"""
Removes litellm api key from headers
"""
clean_headers = {}
litellm_key_lower = (
litellm_key_header_name.lower() if litellm_key_header_name is not None else None
)
for header, value in headers.items():
header_lower = header.lower()
# Preserve Authorization header if it contains Anthropic OAuth token (sk-ant-oat*)
# This allows OAuth tokens to be forwarded to Anthropic-compatible providers
# via add_provider_specific_headers_to_request()
if header_lower == "authorization" and is_anthropic_oauth_key(value):
clean_headers[header] = value
# Check if header should be excluded: either in special headers cache or matches custom litellm key
elif header_lower not in _SPECIAL_HEADERS_CACHE and (
litellm_key_lower is None or header_lower != litellm_key_lower
):
clean_headers[header] = value
return clean_headers
| forwarded_headers[header] = value | ||
| elif header.lower() == "authorization" and is_anthropic_oauth_key(value): | ||
| # Forward Authorization header for Anthropic OAuth tokens (sk-ant-oat*) | ||
| forwarded_headers[header] = value |
There was a problem hiding this comment.
This would be the wrong place to add the header, if you have multiple providers listed in your router, it will forward the authentication header to all the providers listed in your router.
Authorization headers with Anthropic OAuth tokens (sk-ant-oat*) should be
handled separately via add_provider_specific_headers_to_request() to ensure
they only go to Anthropic-compatible providers.
function that should be modified is add_provider_specific_headers_to_request and it should be like:
def add_provider_specific_headers_to_request(
data: dict,
headers: dict,
):
anthropic_headers = {}
# boolean to indicate if a header was added
added_header = False
for header in ANTHROPIC_API_HEADERS:
if header in headers:
header_value = headers[header]
anthropic_headers[header] = header_value
added_header = True
# Check for Authorization header with Anthropic OAuth token (sk-ant-oat*)
# This needs to be handled via provider-specific headers to ensure it only
# goes to Anthropic-compatible providers, not all providers
for header, value in headers.items():
if header.lower() == "authorization" and is_anthropic_oauth_key(value):
anthropic_headers[header] = value
added_header = True
break
if added_header is True:
# Anthropic headers work across multiple providers
# Store as comma-separated list so retrieval can match any of them
data["provider_specific_header"] = ProviderSpecificHeader(
custom_llm_provider=f"{LlmProviders.ANTHROPIC.value},{LlmProviders.BEDROCK.value},{LlmProviders.VERTEX_AI.value}",
extra_headers=anthropic_headers,
)
return
|
|
I've submitted an alternative fix in #19912 that implements the approach suggested in the review comments here. It's a minimal change that:
This ensures OAuth tokens only go to Anthropic-compatible providers (Anthropic/Bedrock/Vertex_AI), not all providers in the router. |
Summary
OAuth tokens require
Authorizationheader (notx-api-key) plus specific beta headers. This change:is_anthropic_oauth_key()to detect OAuth token prefix (sk-ant-oat*)set_anthropic_headers()to set correct auth header based on key typeoptionally_handle_anthropic_oauth()to handle tokens from both Authorization header and directapi_keyparameterRelevant issues
Fixes #19618
Pre-Submission checklist
tests/litellm/directorymake test-unitType
🐛 Bug Fix
Changes
litellm/llms/anthropic/common_utils.pyis_anthropic_oauth_key(),set_anthropic_headers(), updatedoptionally_handle_anthropic_oauth()litellm/llms/anthropic/batches/transformation.pyset_anthropic_headers()for authlitellm/llms/anthropic/completion/transformation.pyset_anthropic_headers()for authlitellm/llms/anthropic/count_tokens/transformation.pyset_anthropic_headers()for authlitellm/llms/anthropic/files/handler.pyset_anthropic_headers()for authlitellm/llms/anthropic/skills/transformation.pyset_anthropic_headers()for authlitellm/llms/anthropic/experimental_pass_through/messages/transformation.pyset_anthropic_headers()for authtests/test_litellm/llms/anthropic/test_anthropic_common_utils.pytests/test_litellm/llms/anthropic/test_anthropic_oauth_manual.pyTest plan
poetry run pytest tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py -v)