From 4f8745fa0b082628188b39b2c63fdbafbe0b893e Mon Sep 17 00:00:00 2001 From: Fix Bot Date: Wed, 20 May 2026 10:02:18 +0000 Subject: [PATCH] fix(azure): send JWT api_key values as Bearer token When an Azure AD access token (JWT starting with "eyJ") is passed via the api_key parameter to AzureOpenAI, send it as Authorization: Bearer instead of api-key: to restore the behavior from v2.33.0 and fix 401 errors from Azure APIM/proxy setups. --- src/openai/lib/azure.py | 14 ++++++++++++-- tests/lib/test_azure.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/openai/lib/azure.py b/src/openai/lib/azure.py index 4fcae24788..bb0a63d07c 100644 --- a/src/openai/lib/azure.py +++ b/src/openai/lib/azure.py @@ -352,6 +352,8 @@ def _auth_headers(self, security: SecurityOptions) -> dict[str, str]: # noqa: A return {"Authorization": f"Bearer {self._azure_ad_token}"} if self.api_key and self.api_key != API_KEY_SENTINEL: + if self.api_key.startswith("eyJ"): + return {"Authorization": f"Bearer {self.api_key}"} return {"api-key": self.api_key} return {} @@ -377,7 +379,10 @@ def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions: if not _has_header(headers, "Authorization"): headers["Authorization"] = f"Bearer {azure_ad_token}" elif self.api_key and self.api_key != API_KEY_SENTINEL: - if not _has_header(headers, "api-key"): + if self.api_key.startswith("eyJ"): + if not _has_header(headers, "Authorization"): + headers["Authorization"] = f"Bearer {self.api_key}" + elif not _has_header(headers, "api-key"): headers["api-key"] = self.api_key elif _has_auth_header(headers) or _has_auth_header(self.default_headers): pass @@ -674,6 +679,8 @@ def _auth_headers(self, security: SecurityOptions) -> dict[str, str]: # noqa: A return {"Authorization": f"Bearer {self._azure_ad_token}"} if self.api_key and self.api_key != API_KEY_SENTINEL: + if self.api_key.startswith("eyJ"): + return {"Authorization": f"Bearer {self.api_key}"} return {"api-key": self.api_key} return {} @@ -699,7 +706,10 @@ async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOp if not _has_header(headers, "Authorization"): headers["Authorization"] = f"Bearer {azure_ad_token}" elif self.api_key and self.api_key != API_KEY_SENTINEL: - if not _has_header(headers, "api-key"): + if self.api_key.startswith("eyJ"): + if not _has_header(headers, "Authorization"): + headers["Authorization"] = f"Bearer {self.api_key}" + elif not _has_header(headers, "api-key"): headers["api-key"] = self.api_key elif _has_auth_header(headers) or _has_auth_header(self.default_headers): pass diff --git a/tests/lib/test_azure.py b/tests/lib/test_azure.py index 3e1d783e2c..6253d5331f 100644 --- a/tests/lib/test_azure.py +++ b/tests/lib/test_azure.py @@ -140,6 +140,43 @@ def test_enforce_credentials_false_sync_uses_request_authorization_header(respx_ assert calls[0].request.headers.get("api-key") is None +@pytest.mark.respx() +def test_jwt_api_key_sent_as_bearer_sync(respx_mock: MockRouter) -> None: + respx_mock.post( + "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01" + ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + + client = AzureOpenAI( + api_version="2024-02-01", + api_key="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.payload.signature", + azure_endpoint="https://example-resource.azure.openai.com", + ) + client.chat.completions.create(messages=[], model="gpt-4") + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert calls[0].request.headers.get("Authorization") == "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.payload.signature" + assert calls[0].request.headers.get("api-key") is None + + +@pytest.mark.asyncio +@pytest.mark.respx() +async def test_jwt_api_key_sent_as_bearer_async(respx_mock: MockRouter) -> None: + respx_mock.post( + "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01" + ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + + client = AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.payload.signature", + azure_endpoint="https://example-resource.azure.openai.com", + ) + await client.chat.completions.create(messages=[], model="gpt-4") + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert calls[0].request.headers.get("Authorization") == "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.payload.signature" + assert calls[0].request.headers.get("api-key") is None + + def test_enforce_credentials_true_sync() -> None: with update_env(AZURE_OPENAI_API_KEY=Omit(), AZURE_OPENAI_AD_TOKEN=Omit()): with pytest.raises(OpenAIError, match="Missing credentials"):