core: add sovereign cloud support via AzureAd configuration#507
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds configurable cloud-specific authentication endpoints to core bot authentication so sovereign cloud deployments can override Bot Framework OIDC metadata and Entra login instance values via configuration.
Changes:
- Adds
OpenIdMetadataUrlandEntraInstancetoBotConfig, sourced from the configured auth section with public-cloud defaults. - Updates JWT signing-key discovery to use these resolved values instead of hardcoded constants.
- Adds unit tests covering default and overridden
BotConfigvalues.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
core/src/Microsoft.Teams.Core/Hosting/BotConfig.cs |
Adds configurable sovereign-cloud endpoint properties and resolves them from configuration. |
core/src/Microsoft.Teams.Core/Hosting/JwtExtensions.cs |
Uses resolved Bot Framework and Entra endpoint values during JWT signing-key discovery. |
core/test/Microsoft.Teams.Core.UnitTests/Hosting/BotConfigTests.cs |
Adds tests for default and configured endpoint resolution. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Adds sovereign cloud support for inbound JWT validation by reading two URLs from the AzureAd configuration section (or whatever section name is passed to AddBotAuthentication): - AzureAd:OpenIdMetadataUrl: Bot Framework OIDC metadata endpoint used to fetch signing keys for inbound Bot Framework tokens. Defaults to the public-cloud endpoint when not set. - AzureAd:Instance: Entra login instance used when validating Entra-issued tokens. Standard Microsoft.Identity.Web key. Defaults to https://login.microsoftonline.com/ when not set. Example sovereign appsettings.json: { "AzureAd": { "TenantId": "...", "ClientId": "...", "Instance": "https://login.microsoftonline.us/", "OpenIdMetadataUrl": "https://login.botframework.azure.us/v1/.well-known/openid-configuration" } } The api.botframework.com issuer string stays hardcoded; it is constant across clouds for Bot Framework tokens. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes from PR review: 1. ValidateTeamsIssuer now derives the expected Entra v2.0 issuer from the configured EntraInstance instead of hardcoding the public-cloud URL. Without this, sovereign Entra tokens would fetch signing keys successfully but get rejected at issuer validation. The v1.0 sts.windows.net issuer check stays in place (covers commercial v1.0 tokens). 2. AddTeamsJwtBearer no longer requires IConfiguration to be registered. The manual-credentials overload AddBotAuthentication(clientId, tenantId, ...) is documented to work on a plain ServiceCollection without configuration, but the previous version called BotConfig.Resolve unconditionally, which throws when IConfiguration is absent. Now falls back to public-cloud defaults when IConfiguration is not registered. Test coverage: - ValidateTeamsIssuer: BF issuer, public Entra v2.0, sovereign Entra v2.0, rejection of public issuer when sovereign Instance configured, v1.0 sts.windows.net path. - AddBotAuthentication manual overload does not throw when no IConfiguration is registered. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a third sovereign-aware property on BotConfig populated from the AzureAd section, matching the existing OpenIdMetadataUrl and Instance pattern: - AzureAd:BotTokenIssuer: expected issuer claim on inbound Bot Framework tokens. Defaults to https://api.botframework.com. For sovereign clouds set to e.g. "https://api.botframework.us" for USGov. Replaces the two hardcoded "https://api.botframework.com" literals in JwtExtensions (the OIDC routing branch and the early-accept inside ValidateTeamsIssuer) with the configured value. Without this, sovereign Bot Framework tokens (which carry a per-cloud issuer claim) would fall through to the Entra validation branch, fail issuer validation, and be rejected even with OpenIdMetadataUrl pointing at the correct sovereign OIDC URL. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous commit threaded BotTokenIssuer through the issuer validator but missed the matching branch in IssuerSigningKeyResolver, which still compared against a hardcoded "https://api.botframework.com" literal. Effect: sovereign BF tokens (iss="https://api.botframework.us" etc.) were routed to the Entra metadata URL for signing-key resolution and failed signature validation before the issuer validator could accept them. Extracts the authority-picking logic into ResolveSigningAuthority for unit testability, and routes any token whose iss matches the configured BotTokenIssuer to BotOIDC. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ff2e752 to
61861cb
Compare
There was a problem hiding this comment.
added some comments.
Not sure the new fields should live inside the AzureAd section... maybe in a new section ?
@corinagum does this PR includes the changes in #483?
|
@rido-min regarding your question:
No, #483 is a separate v1 issuer fix; v1 issuer exists across every cloud. This PR doesn't port those changes because |
- OpenIdMetadataUrl and BotTokenIssuer now read from a new BotFramework section, separating Bot-Framework-specific keys from MSAL's AzureAd. - AzureAd:Instance still reads from the AzureAd section (MSAL convention). - Validate that each override is an absolute URI; throw InvalidOperationException with the section:key path on malformed input. - Consolidate CA1056 suppressions in GlobalSuppressions.cs. - Wrap the Bot Framework issuer-equality branch in braces.
Summary
Enables sovereign cloud (GCC-High, DoD, China) Bot Framework token validation by making the three cloud-dependent values in
JwtExtensionsconfigurable, mirroring theIConfigurationpattern already used byUserTokenClient.Three new properties on
BotConfig, populated byBotConfig.Resolve:EntraInstanceAzureAd:Instancehttps://login.microsoftonline.com/OpenIdMetadataUrlBotFramework:OpenIdMetadataUrlhttps://login.botframework.com/v1/.well-known/openid-configurationBotTokenIssuerBotFramework:BotTokenIssuerhttps://api.botframework.comissclaim on inbound Bot Framework tokensBot-Framework-specific keys live in a separate
BotFrameworkconfiguration section so they are not conflated with MSAL'sAzureAdsection.Instanceremains underAzureAdbecause it is a standard Microsoft.Identity.Web key.Each override is validated as an absolute URI in
BotConfig.Resolve; malformed values throwInvalidOperationExceptionwith thesection:keypath so misconfiguration fails fast instead of deferring to a runtime JWT failure.JwtExtensions.AddTeamsJwtBearerresolvesBotConfigonce and uses these values in both the issuer-signing-key resolver (ResolveSigningAuthority) and the issuer validator (ValidateTeamsIssuer). WhenIConfigurationis not registered (manual-credentials callers on a plainServiceCollection), all three fall back to the public-cloud defaults.Example sovereign config (USGov)
{ "AzureAd": { "TenantId": "...", "ClientId": "...", "Instance": "https://login.microsoftonline.us/" }, "BotFramework": { "OpenIdMetadataUrl": "https://login.botframework.azure.us/v1/.well-known/openid-configuration", "BotTokenIssuer": "https://api.botframework.us" } }Out of scope
Authorityfallback forEntraInstanceis intentionally not implemented.Instanceis the canonical Microsoft.Identity.Web field and matches the keys Microsoft Learn uses for sovereign cloud configuration.BotConfigsource formats other than theAzureAdschema are not extended in this PR.sts.windows.netissuer validation remains accepted (covers commercial v1.0 tokens).Test plan
dotnet build core/core.slnxclean (0 warnings, 0 errors)dotnet test core/test/Microsoft.Teams.Core.UnitTests(130 passed)OpenIdMetadataUrlfor key resolutionInvalidOperationExceptionwith the section:key pathAddBotAuthentication(clientId, tenantId)overload does not requireIConfiguration🤖 Generated with Claude Code