Files
litellm/docs/my-website/docs/proxy_auth.md
T
a6c30b30bf build: migrate packaging, CI, and Docker from Poetry to uv (#25007)
* build: migrate packaging metadata to uv

* ci: move automation and local tooling to uv

* docker: migrate image builds and runtime setup to uv

* docs: update install and deployment guidance for uv

* chore: align auxiliary scripts and tests with uv

* test: harden test_litellm isolation

* fix: keep release and health check images self-contained

* build: pin uv tooling and health check deps

* test: isolate bedrock image request formatting from suite state

* test: cover sandbox executor requirements flow

* ci: fix circleci no-op command steps

* ci: fix circleci publish workflow parsing

* fix: stabilize remaining uv migration CI checks

* ci: increase matrix test timeout headroom

* fix: restore published docker and license coverage

* fix: restore proxy runtime build parity

* fix: restore proxy extras parity and venv migrations

* ci: persist uv path across circleci steps

* fix: keep psycopg binary in default test env

* docker: preserve prisma cache across stages

* test: run local proxy checks through uv python

* build: restore runtime deps moved into ci

* build: refresh uv lock after upstream merge

* fix: restore module import in test_check_migration after merge

The conflict resolution imported only the function but the test body
references check_migration as a module throughout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: revert dependency promotions, remove nodejs-wheel-binaries, fix Docker layer caching

- Move google-generativeai, Pillow, tenacity back to ci group (they are
  lazily imported and bloat the base SDK install needlessly)
- Remove nodejs-wheel-binaries from extra_proxy and proxy-dev (redundant
  in Docker where system Node.js is already installed via apk)
- Remove all nodejs-wheel node replacement and venv npm patching blocks
  from Dockerfiles since the wheel is no longer installed
- Add --no-default-groups to CodSpeed benchmark workflow so the benchmark
  environment matches the old minimal pip install footprint
- Apply standard uv two-phase Docker pattern: copy metadata first, install
  deps (cached layer), then copy source and install project
- Replace CircleCI enterprise no-op with proper uv sync command

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate uv.lock after removing nodejs-wheel-binaries

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): use cache/restore instead of cache to prevent cache poisoning

The old workflow used actions/cache/restore (read-only). The uv migration
changed it to actions/cache (read-write), which zizmor flags as a cache
poisoning risk. Restore the safer read-only variant.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): disable setup-uv built-in cache to silence cache-poisoning alert

The setup-uv action enables caching by default, which zizmor flags as a
cache poisoning risk. Disable it since we already use a read-only
cache/restore step.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): disable setup-uv cache in publish workflow

Silences zizmor cache-poisoning alert. Publishing workflow runs
infrequently on protected branches so caching adds no real benefit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(test): remove duplicate verbose_logger mock in test_check_migration

The logger was patched twice — first via mocker.patch() then via
mocker.patch.object(autospec=True). The second call fails because
autospec cannot inspect an already-mocked attribute. Remove the
redundant first patch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): free disk space before Docker build in test-server-root-path

The Dockerfile.non_root build ran out of disk on the CI runner. Remove
Android SDK, .NET, Boost, and GHC toolchains (~12GB) to free space.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:46:23 -07:00

10 KiB

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

SDK Proxy Authentication (OAuth2/JWT Auto-Refresh)

Automatically obtain and refresh OAuth2/JWT tokens when using the LiteLLM Python SDK with a LiteLLM Proxy that requires JWT authentication.

Overview

When your LiteLLM Proxy is protected by an OAuth2/OIDC provider (Azure AD, Keycloak, Okta, Auth0, etc.), your SDK clients need valid JWT tokens for every request. Instead of manually managing token lifecycle, litellm.proxy_auth handles this automatically:

  • Obtains tokens from your identity provider
  • Caches tokens to avoid unnecessary requests
  • Refreshes tokens before they expire (60-second buffer)
  • Injects Authorization: Bearer <token> headers into every request

Quick Start

Azure AD

Uses the DefaultAzureCredential chain (environment variables, managed identity, Azure CLI, etc.):

import litellm
from litellm.proxy_auth import AzureADCredential, ProxyAuthHandler

# One-time setup
litellm.proxy_auth = ProxyAuthHandler(
    credential=AzureADCredential(),  # uses DefaultAzureCredential
    scope="api://my-litellm-proxy/.default"
)
litellm.api_base = "https://my-proxy.example.com"

# All requests now include Authorization headers automatically
response = litellm.completion(
    model="gpt-4",
    messages=[{"role": "user", "content": "Hello!"}]
)

Use a specific Azure AD app registration:

import litellm
from azure.identity import ClientSecretCredential
from litellm.proxy_auth import AzureADCredential, ProxyAuthHandler

azure_cred = ClientSecretCredential(
    tenant_id="your-tenant-id",
    client_id="your-client-id",
    client_secret="your-client-secret"
)

litellm.proxy_auth = ProxyAuthHandler(
    credential=AzureADCredential(credential=azure_cred),
    scope="api://my-litellm-proxy/.default"
)
litellm.api_base = "https://my-proxy.example.com"

response = litellm.completion(
    model="gpt-4",
    messages=[{"role": "user", "content": "Hello!"}]
)

Required package: uv add azure-identity

Generic OAuth2 (Okta, Auth0, Keycloak, etc.)

Works with any OAuth2 provider that supports the client_credentials grant type:

import litellm
from litellm.proxy_auth import GenericOAuth2Credential, ProxyAuthHandler

litellm.proxy_auth = ProxyAuthHandler(
    credential=GenericOAuth2Credential(
        client_id="your-client-id",
        client_secret="your-client-secret",
        token_url="https://your-idp.example.com/oauth2/token"
    ),
    scope="litellm_proxy_api"
)
litellm.api_base = "https://my-proxy.example.com"

response = litellm.completion(
    model="gpt-4",
    messages=[{"role": "user", "content": "Hello!"}]
)

Custom Credential Provider

Implement the TokenCredential protocol to use any authentication mechanism:

import time
import litellm
from litellm.proxy_auth import AccessToken, ProxyAuthHandler

class MyCustomCredential:
    """Any class with a get_token(scope) -> AccessToken method works."""

    def get_token(self, scope: str) -> AccessToken:
        # Your custom logic to obtain a token
        token = my_auth_system.get_jwt(scope=scope)
        return AccessToken(
            token=token,
            expires_on=int(time.time()) + 3600
        )

litellm.proxy_auth = ProxyAuthHandler(
    credential=MyCustomCredential(),
    scope="my-scope"
)

Supported Endpoints

Auth headers are automatically injected for:

Endpoint Function
Chat Completions litellm.completion() / litellm.acompletion()
Embeddings litellm.embedding() / litellm.aembedding()

How It Works

┌──────────┐     ┌──────────────────┐     ┌──────────────┐     ┌──────────────┐
│  Your    │     │  ProxyAuthHandler │     │   Identity   │     │  LiteLLM     │
│  Code    │────▶│  (token cache)   │────▶│   Provider   │     │  Proxy       │
│          │     │                  │◀────│  (Azure AD,  │     │              │
│          │     │                  │     │   Okta, etc) │     │              │
│          │     └────────┬─────────┘     └──────────────┘     │              │
│          │              │ Authorization: Bearer <token>      │              │
│          │──────────────┼───────────────────────────────────▶│              │
│          │◀─────────────┼────────────────────────────────────│              │
└──────────┘              │                                    └──────────────┘
  1. You set litellm.proxy_auth once at startup
  2. On each SDK call (completion(), embedding()), the handler checks its cached token
  3. If the token is missing or expires within 60 seconds, it requests a new one from your identity provider
  4. The Authorization: Bearer <token> header is injected into the request
  5. If token retrieval fails, a warning is logged and the request proceeds without auth headers

API Reference

ProxyAuthHandler

The main handler that manages the token lifecycle.

from litellm.proxy_auth import ProxyAuthHandler

handler = ProxyAuthHandler(
    credential=<TokenCredential>,  # required - credential provider
    scope="<oauth2-scope>"         # required - OAuth2 scope to request
)
Parameter Type Required Description
credential TokenCredential Yes A credential provider (AzureADCredential, GenericOAuth2Credential, or custom)
scope str Yes The OAuth2 scope to request tokens for

Methods:

Method Returns Description
get_token() AccessToken Get a valid token, refreshing if needed
get_auth_headers() dict Get {"Authorization": "Bearer <token>"} headers

AzureADCredential

Wraps any azure-identity credential with lazy initialization.

from litellm.proxy_auth import AzureADCredential

# Uses DefaultAzureCredential (recommended)
cred = AzureADCredential()

# Or wrap a specific azure-identity credential
from azure.identity import ManagedIdentityCredential
cred = AzureADCredential(credential=ManagedIdentityCredential())
Parameter Type Required Description
credential Azure TokenCredential No An azure-identity credential. If None, uses DefaultAzureCredential

GenericOAuth2Credential

Standard OAuth2 client credentials flow for any provider.

from litellm.proxy_auth import GenericOAuth2Credential

cred = GenericOAuth2Credential(
    client_id="your-client-id",
    client_secret="your-client-secret",
    token_url="https://your-idp.com/oauth2/token"
)
Parameter Type Required Description
client_id str Yes OAuth2 client ID
client_secret str Yes OAuth2 client secret
token_url str Yes Token endpoint URL

AccessToken

Dataclass representing an OAuth2 access token.

from litellm.proxy_auth import AccessToken

token = AccessToken(
    token="eyJhbG...",     # JWT string
    expires_on=1234567890  # Unix timestamp
)

TokenCredential Protocol

Any class implementing this protocol can be used as a credential provider:

from litellm.proxy_auth import AccessToken

class MyCredential:
    def get_token(self, scope: str) -> AccessToken:
        ...

Provider-Specific Examples

Keycloak

from litellm.proxy_auth import GenericOAuth2Credential, ProxyAuthHandler

litellm.proxy_auth = ProxyAuthHandler(
    credential=GenericOAuth2Credential(
        client_id="litellm-client",
        client_secret="your-keycloak-client-secret",
        token_url="https://keycloak.example.com/realms/your-realm/protocol/openid-connect/token"
    ),
    scope="openid"
)

Okta

from litellm.proxy_auth import GenericOAuth2Credential, ProxyAuthHandler

litellm.proxy_auth = ProxyAuthHandler(
    credential=GenericOAuth2Credential(
        client_id="your-okta-client-id",
        client_secret="your-okta-client-secret",
        token_url="https://your-org.okta.com/oauth2/default/v1/token"
    ),
    scope="litellm_api"
)

Auth0

from litellm.proxy_auth import GenericOAuth2Credential, ProxyAuthHandler

litellm.proxy_auth = ProxyAuthHandler(
    credential=GenericOAuth2Credential(
        client_id="your-auth0-client-id",
        client_secret="your-auth0-client-secret",
        token_url="https://your-tenant.auth0.com/oauth/token"
    ),
    scope="https://my-proxy.example.com/api"
)

Azure AD with Managed Identity

from azure.identity import ManagedIdentityCredential
from litellm.proxy_auth import AzureADCredential, ProxyAuthHandler

litellm.proxy_auth = ProxyAuthHandler(
    credential=AzureADCredential(
        credential=ManagedIdentityCredential()
    ),
    scope="api://my-litellm-proxy/.default"
)

Combining with use_litellm_proxy

You can use proxy_auth together with use_litellm_proxy to route all SDK requests through an authenticated proxy:

import os
import litellm
from litellm.proxy_auth import AzureADCredential, ProxyAuthHandler

# Route all requests through the proxy
os.environ["LITELLM_PROXY_API_BASE"] = "https://my-proxy.example.com"
litellm.use_litellm_proxy = True

# Authenticate with OAuth2/JWT
litellm.proxy_auth = ProxyAuthHandler(
    credential=AzureADCredential(),
    scope="api://my-litellm-proxy/.default"
)

# This request goes through the proxy with automatic JWT auth
response = litellm.completion(
    model="vertex_ai/gemini-2.0-flash-001",
    messages=[{"role": "user", "content": "Hello!"}]
)