Skip to content

fix(anthropic): properly handle OAuth tokens (sk-ant-oat*) across all…#19802

Closed
tompipe wants to merge 3 commits intoBerriAI:mainfrom
tompipe:fix/anthropic-oauth-token-handling
Closed

fix(anthropic): properly handle OAuth tokens (sk-ant-oat*) across all…#19802
tompipe wants to merge 3 commits intoBerriAI:mainfrom
tompipe:fix/anthropic-oauth-token-handling

Conversation

@tompipe
Copy link

@tompipe tompipe commented Jan 26, 2026

Summary

OAuth tokens require Authorization header (not x-api-key) plus specific beta headers. This change:

  • Adds is_anthropic_oauth_key() to detect OAuth token prefix (sk-ant-oat*)
  • Adds set_anthropic_headers() to set correct auth header based on key type
  • Updates optionally_handle_anthropic_oauth() to handle tokens from both Authorization header and direct api_key parameter
  • Applies OAuth handling consistently across all Anthropic endpoints: batches, completion, count_tokens, files, skills, and pass-through
  • Adds unit tests and manual integration test suite

Relevant issues

Fixes #19618

Pre-Submission checklist

  • I have Added testing in the tests/litellm/ directory
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem

Type

🐛 Bug Fix

Changes

File Change
litellm/llms/anthropic/common_utils.py Added is_anthropic_oauth_key(), set_anthropic_headers(), updated optionally_handle_anthropic_oauth()
litellm/llms/anthropic/batches/transformation.py Use set_anthropic_headers() for auth
litellm/llms/anthropic/completion/transformation.py Use set_anthropic_headers() for auth
litellm/llms/anthropic/count_tokens/transformation.py Use set_anthropic_headers() for auth
litellm/llms/anthropic/files/handler.py Use set_anthropic_headers() for auth
litellm/llms/anthropic/skills/transformation.py Use set_anthropic_headers() for auth
litellm/llms/anthropic/experimental_pass_through/messages/transformation.py Use set_anthropic_headers() for auth
tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py 6 unit tests for OAuth handling
tests/test_litellm/llms/anthropic/test_anthropic_oauth_manual.py Manual integration tests (skipped in CI)

Test plan

  • Unit tests pass (poetry run pytest tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py -v)
  • Manual integration tests verified with real OAuth token

@vercel
Copy link

vercel bot commented Jan 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
litellm Ready Ready Preview, Comment Jan 27, 2026 0:02am

Request Review

@CLAassistant
Copy link

CLAassistant commented Jan 26, 2026

CLA assistant check
All committers have signed the CLA.

… 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
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>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@ghost ghost added the changes requested label Jan 28, 2026
@ghost
Copy link

ghost commented Jan 28, 2026

@iamadamreed
Copy link
Contributor

I've submitted an alternative fix in #19912 that implements the approach suggested in the review comments here. It's a minimal change that:

  1. Adds is_anthropic_oauth_key() helper
  2. Modifies clean_headers() to preserve OAuth Authorization header
  3. Modifies add_provider_specific_headers_to_request() to forward OAuth via provider-specific headers

This ensures OAuth tokens only go to Anthropic-compatible providers (Anthropic/Bedrock/Vertex_AI), not all providers in the router.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] OAuth tokens not forwarded to Anthropic - PR #19453 incomplete

4 participants