Skip to content

Support managed identity auth in AzureFoundry provider#9707

Open
Evangelink wants to merge 3 commits into
mainfrom
dev/amauryleve/azurefoundry-managed-identity-auth
Open

Support managed identity auth in AzureFoundry provider#9707
Evangelink wants to merge 3 commits into
mainfrom
dev/amauryleve/azurefoundry-managed-identity-auth

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Fixes #8412

Summary

Microsoft.Testing.Extensions.AzureFoundry previously hard-required AZURE_OPENAI_API_KEY and always constructed AzureOpenAIClient with ApiKeyCredential, with no managed-identity / Entra path. This made the only built-in AI provider not secure-by-default for Azure-hosted scenarios.

Changes

  • IsAvailable now only requires AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_DEPLOYMENT_NAME; the API key is no longer mandatory.
  • CreateChatClientAsync prefers an explicit AZURE_OPENAI_API_KEY when present, otherwise falls back to DefaultAzureCredential (managed identity, workload identity, Azure CLI, Visual Studio, …).
  • Added the Azure.Identity package reference (version pinned in Directory.Packages.props).
  • Updated PACKAGE.md with a configuration table documenting both authentication modes.

Notes

  • No .resx/.xlf changes were needed — the existing EnvironmentVariableNotSet string is still used for the endpoint/deployment checks.
  • Built cleanly (netstandard2.0, net8.0, net9.0) with 0 warnings/0 errors.

The AzureFoundry chat client provider now authenticates with
DefaultAzureCredential (Entra ID / managed identity) when no
AZURE_OPENAI_API_KEY is set, keeping the provider secure-by-default
for Azure-hosted scenarios. The API key remains an explicit fallback,
and IsAvailable no longer requires an API key.

Fixes #8412

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 7, 2026 14:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR makes the Microsoft.Testing.Extensions.AzureFoundry provider support Microsoft Entra ID / managed-identity authentication instead of hard-requiring AZURE_OPENAI_API_KEY, closing the secure-by-default gap described in issue #8412. The provider now reports itself available whenever the endpoint and deployment name are configured, and it authenticates via DefaultAzureCredential when no API key is supplied, keeping the explicit API-key path as an override.

Changes:

  • IsAvailable no longer requires AZURE_OPENAI_API_KEY (only endpoint + deployment name).
  • CreateChatClientAsync uses ApiKeyCredential when a key is present, otherwise falls back to DefaultAzureCredential.
  • Adds the Azure.Identity dependency (version pinned via CPM) and documents both auth modes in PACKAGE.md.
Show a summary per file
File Description
src/Platform/Microsoft.Testing.Extensions.AzureFoundry/OpenAIChatClientProvider.cs Drops the mandatory API-key check and selects DefaultAzureCredential vs ApiKeyCredential based on whether the key is set.
src/Platform/Microsoft.Testing.Extensions.AzureFoundry/PACKAGE.md Adds a configuration table and prose documenting the managed-identity and API-key authentication modes.
src/Platform/Microsoft.Testing.Extensions.AzureFoundry/Microsoft.Testing.Extensions.AzureFoundry.csproj References the new Azure.Identity package.
Directory.Packages.props Pins Azure.Identity version 1.14.2 under central package management.

Review details

  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Medium

Comment thread Directory.Packages.props Outdated
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.4" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.1.0" />
<PackageVersion Include="Azure.Identity" Version="1.14.2" />

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — bumped Azure.Identity from 1.14.2 to 1.21.0 (the current supported release; 1.14.x is deprecated because it depends on a deprecated MSAL). Verified a clean restore/build across all TFMs (netstandard2.0, net8.0, net9.0, net462, net472) with no NU1605 downgrade or NuGetAudit warnings, and it resolves Azure.Core upward compatibly with Azure.AI.OpenAI 2.1.0.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

🤖 Automated review by GitHub Copilot. Generated by the Expert Code Review workflow. To request a follow-up action, reply by tagging @copilot directly.

# Dimension Verdict
13 Test Completeness 🟡 1 MODERATE
20 Build Infrastructure & Dependencies 🟡 1 MODERATE
17 Documentation Accuracy ⚪ 1 NIT

✅ 19/22 dimensions clean (dimensions 10–12, 14, 18–19, 22 skipped as N/A).

  • Test Completeness — no unit tests cover the new DefaultAzureCredential branch (or any path of AzureOpenAIChatClientProvider)
  • Build Infrastructure — Azure.Identity adds a heavyweight transitive dependency graph; acceptable while alpha/non-shipping, but worth revisiting before GA
  • Documentation — consider noting local-dev latency from DefaultAzureCredential credential chain traversal

Overall: The implementation is clean, correct, and well-documented. The ternary conditional for credential selection is straightforward. The IsAvailable change correctly reflects the new optionality of the API key. No correctness, threading, security, or API compatibility concerns.

// authentication via DefaultAzureCredential. This keeps the provider secure-by-default for
// Azure-hosted scenarios where distributing API keys is undesirable.
AzureOpenAIClient client = RoslynString.IsNullOrEmpty(apiKey)
? new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[MODERATE] Test Completeness

The DefaultAzureCredential path is new behavior with no unit test coverage. There are no existing tests for AzureOpenAIChatClientProvider at all — neither the API-key path nor the new managed-identity path is exercised.

Consider adding unit tests (even if they only verify the correct branch is taken and the right client type is constructed) to prevent regressions. The class is internal so [InternalsVisibleTo] or a test-specific seam would be needed.

Recommendation: Add at least a basic test verifying that when AZURE_OPENAI_API_KEY is unset, the code doesn't throw InvalidOperationException (the old behavior) and that when it is set, ApiKeyCredential is used.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed — added a dedicated unit test project reference (Microsoft.Testing.Extensions.UnitTests with InternalsVisibleTo) covering AzureOpenAIChatClientProvider. It now exercises IsAvailable (API-key-optional path), ModelName, HasToolsCapability, the missing-endpoint/deployment InvalidOperationException cases, and the API-key-vs-DefaultAzureCredential branch selection via an internal GetAuthenticationMode seam (14 tests, all passing).

| --- | --- | --- |
| `AZURE_OPENAI_ENDPOINT` | Yes | The Azure OpenAI resource endpoint, for example `https://my-resource.openai.azure.com`. |
| `AZURE_OPENAI_DEPLOYMENT_NAME` | Yes | The name of the model deployment to use. |
| `AZURE_OPENAI_API_KEY` | No | An API key for the resource. When omitted, authentication uses `DefaultAzureCredential` (Managed Identity, Workload Identity, Azure CLI, Visual Studio, etc.). |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[NIT] Documentation Accuracy

Consider adding a brief note that DefaultAzureCredential traverses a credential chain (Managed Identity → Workload Identity → Azure CLI → Visual Studio → etc.) which can add noticeable latency in local development scenarios where early credentials in the chain time out before reaching one that works (e.g., Azure CLI). This is a common source of confusion for developers trying the package locally for the first time.

Something like:

Note: In local development, DefaultAzureCredential may take several seconds on first use as it probes multiple credential sources. Providing AZURE_OPENAI_API_KEY avoids this delay.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done — added a note to PACKAGE.md that DefaultAzureCredential probes a credential chain and can add several seconds of latency on first use in local development, and that setting AZURE_OPENAI_API_KEY avoids the delay.

Comment thread Directory.Packages.props Outdated
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.4" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.1.0" />
<PackageVersion Include="Azure.Identity" Version="1.14.2" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[MODERATE] Build Infrastructure & Dependencies

Azure.Identity 1.14.2 is a heavyweight dependency — it transitively pulls in MSAL (Microsoft.Identity.Client), Azure.Core, System.Memory, Microsoft.IdentityModel.*, and others. This becomes a transitive dependency for all consumers of the Microsoft.Testing.Extensions.AzureFoundry NuGet package.

Since the package is currently alpha / IsShipping=false, this is acceptable for now. However, when this package moves to shipping status, consider whether Azure.Identity should remain a direct dependency or whether the credential creation should be abstracted (e.g., accept a TokenCredential parameter) so consumers who don't need managed identity aren't forced to pull the full MSAL graph.

@Evangelink Evangelink Jul 7, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed. The package is currently alpha / IsShipping=false, so the transitive MSAL/Azure.Core graph is acceptable for now. I've filed a follow-up to revisit before GA — either keep Azure.Identity as a direct dependency or abstract credential creation behind an injectable TokenCredential so consumers who only use API keys don't pull the full MSAL graph. Tracking issue: #9712.

- Reuse a single DefaultAzureCredential instance (Lazy) so the token
  cache and credential-chain discovery are shared across calls instead
  of being rebuilt on every CreateChatClientAsync invocation.
- Add unit tests for the auth-selection logic (IsAvailable, ModelName,
  API-key vs Entra path, missing-variable failures) in
  Microsoft.Testing.Extensions.UnitTests, with env-var snapshot/restore
  and DoNotParallelize; grant InternalsVisibleTo to the test assembly.
- Document AZURE_CLIENT_ID (user-assigned managed identity) and the
  deferred authentication-failure behavior in PACKAGE.md.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

- Bump Azure.Identity 1.14.2 -> 1.21.0. The 1.14.x line is deprecated
  (depends on a deprecated MSAL); 1.21.0 is the current supported
  release. Verified clean restore/build across all TFMs (no NU1605,
  no audit warnings) and compatible with Azure.AI.OpenAI 2.1.0.
- Extract an internal AuthenticationMode + GetAuthenticationMode seam so
  the API-key-vs-DefaultAzureCredential selection is deterministically
  unit-tested (upgrades the two 'only IsNotNull' tests and adds three
  dedicated selection tests).
- Document DefaultAzureCredential local-dev credential-chain latency in
  PACKAGE.md.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Low

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🧪 Test quality grade — PR #9707

14 new tests graded across 1 file. 12 earn A for clean AAA structure, proper env-var isolation via [DoNotParallelize] + [TestInitialize]/[TestCleanup] snapshot/restore, and precise assertions (equality, boolean state, or exact exception type + message). 2 earn B for mixing two distinct behavioral checks in one test body — extracting the GetAuthenticationMode assertions into their own tests would sharpen those two.

GradeTestNotes
B (80–89) new AzureFoundryChatClientProviderTests.
CreateChatClientAsync_
WithApiKey_
UsesApiKeyPathAndReturnsClient
Mixes a GetAuthenticationMode equality check with IsNotNull on the client — consider separate focused tests per concern.
B (80–89) new AzureFoundryChatClientProviderTests.
CreateChatClientAsync_
WithoutApiKey_
UsesEntraPathAndReturnsClient
Dual-behavior pattern; GetAuthenticationMode(null) is already covered by a dedicated test — consider removing it from this body.
A (90–100) new AzureFoundryChatClientProviderTests.
CreateChatClientAsync_
WhenDeploymentMissing_
Throws
Exact exception type + message content verifies the deployment-missing error path. No issues found.
A (90–100) new AzureFoundryChatClientProviderTests.
CreateChatClientAsync_
WhenEndpointMissing_
Throws
Exact exception type + message content verifies the endpoint-missing error path. No issues found.
A (90–100) new AzureFoundryChatClientProviderTests.
GetAuthenticationMode_
WithApiKey_
ReturnsApiKey
Terse expression-bodied; precise equality confirms non-empty key routes to ApiKey mode. No issues found.
A (90–100) new AzureFoundryChatClientProviderTests.
GetAuthenticationMode_
WithEmptyApiKey_
ReturnsDefaultAzureCredential
Edge case covered; empty string triggers managed identity auth identically to null. No issues found.
A (90–100) new AzureFoundryChatClientProviderTests.
GetAuthenticationMode_
WithoutApiKey_
ReturnsDefaultAzureCredential
Terse; null maps to DefaultAzureCredential. No issues found.
A (90–100) new AzureFoundryChatClientProviderTests.
HasToolsCapability_
ReturnsTrue
State assertion on the public contract; constant property — test guards against accidental future changes. No issues found.
A (90–100) new AzureFoundryChatClientProviderTests.
IsAvailable_
WhenApiKeyAlsoSet_
ReturnsTrue
Confirms availability is unchanged when an API key is also provided. No issues found.
A (90–100) new AzureFoundryChatClientProviderTests.
IsAvailable_
WhenDeploymentMissing_
ReturnsFalse
Negative test; missing deployment variable prevents availability. No issues found.
A (90–100) new AzureFoundryChatClientProviderTests.
IsAvailable_
WhenEndpointAndDeploymentSetWithoutApiKey_
ReturnsTrue
Positive test for the new no-API-key availability condition. No issues found.
A (90–100) new AzureFoundryChatClientProviderTests.
IsAvailable_
WhenEndpointMissing_
ReturnsFalse
Negative test; missing endpoint variable prevents availability. No issues found.
A (90–100) new AzureFoundryChatClientProviderTests.
ModelName_
ReturnsDeploymentName
Clean equality assertion; deployment variable value is surfaced correctly. No issues found.
A (90–100) new AzureFoundryChatClientProviderTests.
ModelName_
WhenDeploymentMissing_
ReturnsUnknown
Equality assertion covers the "unknown" fallback when no deployment is configured. No issues found.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Re-run with
/grade-tests.

🤖 Automated content by GitHub Copilot. Generated by the Grade Tests on PR (on open / sync) workflow. · 118.1 AIC · ⌖ 10.5 AIC · ⊞ 9.5K · [◷]( · )

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[MTP Diagnostic Analysis] AzureFoundry hard-requires API keys and has no managed-identity auth path

2 participants