Skip to content

Fix Azure OpenAI authentication failure behind a TLS-inspecting proxy#6553

Open
subhashpolisetti wants to merge 1 commit into
spring-projects:mainfrom
subhashpolisetti:azure-openai-auth-example-com
Open

Fix Azure OpenAI authentication failure behind a TLS-inspecting proxy#6553
subhashpolisetti wants to merge 1 commit into
spring-projects:mainfrom
subhashpolisetti:azure-openai-auth-example-com

Conversation

@subhashpolisetti

@subhashpolisetti subhashpolisetti commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Azure OpenAI token authentication (Azure CLI or managed identity, rather than an
API key) currently builds its bearer-token supplier with
AuthenticationUtil.getBearerTokenSupplier(...). That supplier issues a live
HTTPS GET https://www.example.com on every invocation, purely to run the
credential through a BearerTokenAuthenticationPolicy and read the resulting
Authorization header back off the request. The openai-java client invokes
the supplier while preparing each request, so this hop happens on every call.

The example.com request is unnecessary and fails in environments where such a
connection is not permitted for example behind a TLS-inspecting proxy that
requires custom certificates, where it surfaces as
SSLHandshakeException: ... PKIX path building failed. Importing certificates
into every application's JVM is an impractical workaround.

This reads the token directly from the credential instead. Since the
azure-identity credentials do not cache tokens themselves — that is the SDK
pipeline's role — it is wrapped in SimpleTokenCache to preserve the token
caching and refresh the previous pipeline provided. No dependency is added (both
types come with the Azure SDK), the acquired token is unchanged, and it
continues to work with any DefaultAzureCredential chain (managed identity,
workload identity, Azure CLI, etc.).

Testing

Added unit tests in AzureInternalOpenAiHelperTests:

  • the token is resolved from the credential (no HTTP client in the path),
  • the https://cognitiveservices.azure.com/.default scope is requested,
  • the cached token is reused across calls (the credential is queried once).

./mvnw -pl models/spring-ai-openai test passes; formatting is clean.

Closes #6549

@sdeleuze

sdeleuze commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Could you please add an integration test that demonstrates the issue (broken without the fix, passing with the fix)?

@subhashpolisetti

Copy link
Copy Markdown
Contributor Author

I looked into this and couldn't find a way to write a self-contained test that fails without the fix, because the failure is environmental.

The old code used AuthenticationUtil.getBearerTokenSupplier(credential, scope). Its supplier does a sendSync(GET https://www.example.com) on every call, just to read back the Authorization header. The URL is hardcoded and there's no way to inject an HttpClient, so on a normal network (including CI) it succeeds. I checked, and the existing tests actually pass against both the old and new code. It only breaks behind a TLS-inspecting proxy that fails the example.com handshake (the PKIX error from the issue), which I can't reproduce deterministically in CI.

The unit tests I added cover the new behavior: token resolved via getTokenSync with the cognitiveservices.azure.com/.default scope, cached across calls, no example.com round-trip.

The only way I found to force a failure is overriding the default Azure HttpClient via ServiceLoader, but that's JVM-wide and would affect the other tests in the module. Happy to add it if you're fine with that, or take a different approach if you have one in mind.

@subhashpolisetti subhashpolisetti force-pushed the azure-openai-auth-example-com branch from d250028 to 7965e96 Compare July 6, 2026 17:25
Azure OpenAI authentication acquired its bearer token through
AuthenticationUtil.getBearerTokenSupplier, which issues a live HTTPS
request to https://www.example.com on every call purely to read the
Authorization header back off the request. That extra hop is
unnecessary and fails in environments where the connection is not
permitted, such as behind a TLS-inspecting proxy requiring custom
certificates.

Acquire the token from the credential directly, caching it in a small
synchronous supplier that refreshes shortly before expiry. This drops
the example.com request while preserving the token caching the removed
pipeline provided, and keeps token acquisition off the reactive path.

Closes spring-projects#6549

Signed-off-by: subhash polisetti <subhashr161347@gmail.com>
@subhashpolisetti subhashpolisetti force-pushed the azure-openai-auth-example-com branch from 7965e96 to ee4e111 Compare July 7, 2026 00:05
@sdeleuze sdeleuze added this to the 2.0.1 milestone Jul 7, 2026
@sdeleuze sdeleuze added bug Something isn't working and removed status: waiting-for-triage labels Jul 7, 2026
@sdeleuze

sdeleuze commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Ok thanks for the additional context, I will have a deeper look during the review.

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

Labels

azure bug Something isn't working openai

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Spring AI 2 authentication to Azure OpenAI fails

3 participants