BerriAI/litellm · error · AzureOpenAIError

OIDC token could not be retrieved from secret manager.

Error message

OIDC token could not be retrieved from secret manager.

What it means

After client/tenant IDs are resolved, LiteLLM reads the OIDC token via get_secret_str(azure_ad_token). If the value is None — because no azure_ad_token was supplied, or the secret manager lookup returned nothing — it raises AzureOpenAIError 401. This is a guard that the workload-identity assertion JWT must be present before calling the Azure AD token endpoint.

Source

Thrown at litellm/llms/azure/common_utils.py:203

    Returns:
        `azure_ad_token_access_token` - str
    """
    if scope is None:
        scope = "https://cognitiveservices.azure.com/.default"
    azure_authority_host: Final = os.getenv("AZURE_AUTHORITY_HOST", "https://login.microsoftonline.com")
    azure_client_id = azure_client_id or os.getenv("AZURE_CLIENT_ID")
    azure_tenant_id = azure_tenant_id or os.getenv("AZURE_TENANT_ID")
    if azure_client_id is None or azure_tenant_id is None:
        raise AzureOpenAIError(
            status_code=422,
            message="AZURE_CLIENT_ID and AZURE_TENANT_ID must be set",
        )

    oidc_token: Final = get_secret_str(azure_ad_token)

    if oidc_token is None:
        raise AzureOpenAIError(
            status_code=401,
            message="OIDC token could not be retrieved from secret manager.",
        )

    azure_ad_token_cache_key: Final = json.dumps(
        {
            "azure_client_id": azure_client_id,
            "azure_tenant_id": azure_tenant_id,
            "azure_authority_host": azure_authority_host,
            "oidc_token": oidc_token,
        }
    )

    azure_ad_token_access_token = azure_ad_cache.get_cache(azure_ad_token_cache_key)
    if azure_ad_token_access_token is not None:
        return azure_ad_token_access_token

    client: Final = litellm.module_level_client

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Pass a real, non-empty OIDC/JWT string as azure_ad_token when calling LiteLLM.
  2. If using secret-manager references, verify the secret exists and the manager is configured: check get_secret() resolves it (e.g. os.environ for env-based secrets, or your secret backend).
  3. In containers/pods, confirm the workload-identity token file (e.g. AZURE_FEDERATED_TOKEN_FILE) is mounted and your code reads it into azure_ad_token before calling LiteLLM.
  4. If you meant to use key auth, drop azure_ad_token and pass api_key instead.

Example fix

# before
resp = litellm.completion(model="azure/<dep>", messages=msgs, azure_ad_token="")  # empty -> None

# after
oidc = requests.get(
    "http://169.254.169.254/metadata/identity/oauth2/token",
    params={"api-version": "2019-08-01", "resource": "https://cognitiveservices.azure.com"},
    headers={"Metadata": "true"},
).json()["access_token"]
resp = litellm.completion(model="azure/<dep>", messages=msgs, azure_ad_token=oidc)
Defensive patterns

Strategy: validation

Validate before calling

import os

def resolve_oidc_token(token_or_key: str | None) -> str:
    # resolve like LiteLLM: treat a non-JWT value as an env/secret key
    value = os.getenv(token_or_key) if token_or_key and not token_or_key.startswith("ey") else token_or_key
    if not value:
        raise AuthError("OIDC token missing: fetch it from the workload identity endpoint first")
    return value

Type guard

def is_valid_oidc_token(t: object) -> bool:
    return isinstance(t, str) and t.count(".") == 2 and len(t) > 100

Try / catch

try:
    resp = litellm.completion(..., azure_ad_token=oidc)
except AzureOpenAIError as e:
    if e.status_code == 401 and "OIDC token" in str(e):
        oidc = fetch_fresh_oidc()  # metadata endpoint
        resp = litellm.completion(..., azure_ad_token=oidc)
    else:
        raise

Prevention

When it happens

Trigger: AZURE_CLIENT_ID and AZURE_TENANT_ID are set but azure_ad_token is None/empty, or the value passed is a secret-manager key (e.g. 'azure/oidc/token') that the configured secret manager cannot resolve. Also triggered by passing an empty string, which get_secret_str normalizes to None.

Common situations: Federated credentials setup where the app fetches the OIDC token lazily but LiteLLM is called before it is available; secret deleted/rotated in AWS/GCP secret manager while the key reference stayed in config; passing the literal token in one environment but only the key name in another.

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/7c96308aba2ae317. Report an issue: GitHub.