Add sovereign cloud support (GCCH, DoD, China)#352
Conversation
Manual Test ResultsTested locally by linking a scaffolded C# echo bot to this branch via Environment
Tests
ObservationThe config path ( Tested with teams-sdk-dev tooling. |
Updated Test Results (after
|
| # | Test | Result |
|---|---|---|
| 1 | Invalid cloud name ("Cloud": "Narnia") |
Pass. Crashes with: Unknown cloud environment: 'Narnia'. Valid values are: Public, USGov, USGovDoD, China. |
| 2 | Default behavior (no Cloud key) | Pass. Bot starts, echoes messages. Defaults to CloudEnvironment.Public. |
| 3 | Named preset ("Cloud": "Public") |
Pass. Starts normally. |
| 4 | Single endpoint override ("TokenServiceUrl": "https://bogus.example.com/token") |
Pass. Bot starts (override accepted at startup, used at runtime). Proves individual override is wired through ResolveCloud → WithOverrides. |
| 5 | Preset + override ("Cloud": "USGov" + "LoginEndpoint": "https://custom-login.example.com") |
Pass. Bot starts with USGov as base and the custom LoginEndpoint applied on top. |
| 6 | Unit tests (CloudEnvironmentTests) |
Pass. All 21 tests pass, including 4 new WithOverrides tests (all-nulls, single, multiple, all overrides). |
Design Notes
WithOverridesreturnsthiswhen all overrides are null — nice zero-allocation fast path.ResolveCloudchains cleanly: programmaticAppOptions.Cloud→ config"Cloud"preset →Publicfallback → per-endpoint overrides. Priority order is clear.- All 8 endpoint properties are individually overridable from
appsettings.jsonunder theTeamssection.
URL Verification: CloudEnvironment presets vs Bot Framework docsCross-referenced all USGov (GCCH)
USGovDoD
Notes on the two
|
rido-min
left a comment
There was a problem hiding this comment.
Thank you for addressing sovereign cloud support - this is an important capability for government and regional customers.
I have some architectural concerns about the current approach that I'd like to discuss before we proceed:
- Hard-coded endpoint presets create a maintenance burden
The CloudEnvironment class embeds specific URLs for each sovereign cloud (GCCH, DoD, China). These endpoints have historically changed over time - when Microsoft updates them, this requires an SDK release
and forces all consumers to update their SDK version, even though their application logic hasn't changed. This couples deployment cycles unnecessarily.
The MSAL library itself avoids this pattern - it relies on well-known discovery documents (OpenID configuration endpoints) to resolve the actual service URLs at runtime. This approach is more resilient to
endpoint changes.
- Configuration-only approach is more flexible
Rather than providing presets, consider allowing sovereign cloud configuration entirely through settings:
{
"Teams": {
"LoginEndpoint": "https://login.microsoftonline.us",
"TokenServiceUrl": "https://tokengcch.botframework.azure.us",
...
}
}
This puts control in the hands of the deploying organization (who often have specific requirements in sovereign environments) rather than requiring SDK changes.
- Scattered client configuration
The current implementation adds TokenServiceUrl properties to individual clients (BotSignInClient, UserTokenClient, BotTokenClient). This means each client must be configured separately, which is error-prone
and harder to maintain than a centralized configuration that flows through to all clients.
Alternative approach to consider:
- Use the OpenID discovery document (already referenced as OpenIdMetadataUrl) to dynamically resolve endpoints where possible
- If presets are valuable for developer convenience, consider shipping them as a separate configuration file or NuGet package that can be updated independently of the core SDK
- Centralize cloud configuration in one place that all clients consume, rather than scattering it across multiple client classes
Would it be worth discussing these trade-offs before proceeding?
I dont think presets are valuable, we dont own the Entra URLs, and we should not hardcode those URLs in our codebase. @copilot what do you think about the comments in #352 (review) |
a548dc5 to
ffbc525
Compare
96871c5 to
cbd96f9
Compare
There was a problem hiding this comment.
Pull request overview
Adds sovereign cloud (GCCH, DoD, China/21Vianet) configurability to the Teams SDK by introducing a CloudEnvironment abstraction and threading it through auth/token clients, app construction, and configuration/hosting extensions so previously hardcoded endpoints can be selected or overridden.
Changes:
- Introduces
CloudEnvironmentpresets (Public,USGov,USGovDoD,China) plusFromName()andWithOverrides()to centralize cloud-specific endpoints. - Updates auth/token flows to use cloud-specific endpoints/scopes (e.g., configurable Bot Framework scope and token service base URL).
- Adds configuration + hosting resolution (
TeamsSettings.ResolveCloud()) and new tests covering cloud-specific behavior.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| Libraries/Microsoft.Teams.Api/Auth/CloudEnvironment.cs | New centralized cloud endpoint bundle with presets + name resolution + overrides. |
| Libraries/Microsoft.Teams.Api/Auth/ClientCredentials.cs | Uses cloud login endpoint + tenant for client-credentials token acquisition. |
| Libraries/Microsoft.Teams.Api/Clients/BotTokenClient.cs | Adds overridable bot scope (ActiveBotScope) used for token acquisition. |
| Libraries/Microsoft.Teams.Api/Clients/UserTokenClient.cs | Makes token service base URL configurable via TokenServiceUrl. |
| Libraries/Microsoft.Teams.Api/Clients/BotSignInClient.cs | Makes token service base URL configurable via TokenServiceUrl. |
| Libraries/Microsoft.Teams.Apps/AppOptions.cs | Adds Cloud option to pass cloud environment programmatically. |
| Libraries/Microsoft.Teams.Apps/App.cs | Applies cloud settings to API sub-clients (bot scope + token service URLs). |
| Libraries/Microsoft.Teams.Extensions.Configuration/Microsoft.Teams.Apps.Extensions/TeamsSettings.cs | Adds Cloud + per-endpoint overrides and resolves to a CloudEnvironment. |
| Libraries/Microsoft.Teams.Extensions.Hosting/Microsoft.Teams.Apps.Extensions/HostApplicationBuilder.cs | Resolves cloud from config/programmatic options and wires into credentials/options. |
| Libraries/Microsoft.Teams.Plugins/Microsoft.Teams.Plugins.AspNetCore/Extensions/HostApplicationBuilder.cs | Resolves cloud from config for token validation setup. |
| Libraries/Microsoft.Teams.Plugins/Microsoft.Teams.Plugins.AspNetCore/Extensions/TeamsValidationSettings.cs | Makes validation settings cloud-aware (OpenID metadata URL, issuers, Entra issuer derivation). |
| Tests/Microsoft.Teams.Api.Tests/Auth/CloudEnvironmentTests.cs | New unit tests validating presets, overrides, and name resolution. |
| Tests/Microsoft.Teams.Api.Tests/Clients/BotTokenClientTests.cs | Adds coverage for ActiveBotScope behavior and usage. |
| Tests/Microsoft.Teams.Plugins.AspNetCore.Tests/Extensions/TeamsValidationSettingsTests.cs | New tests validating cloud-aware token validation settings behavior. |
Comments suppressed due to low confidence (1)
Libraries/Microsoft.Teams.Apps/App.cs:60
AppOptions.Cloudis applied to the API clients (bot scope + token service URLs), but it isn’t applied toClientCredentials. In the programmatic usage shown in the PR description (Cloud = CloudEnvironment.USGov+Credentials = new ClientCredentials(...)),ClientCredentials.Cloudwill remainPublicand token acquisition will still hitlogin.microsoftonline.com. Consider propagatingcloudintoClientCredentialswhenCredentialsis aClientCredentialsinstance (or otherwise document/guard against this mismatch).
var cloud = options?.Cloud ?? CloudEnvironment.Public;
Logger = options?.Logger ?? new ConsoleLogger();
Storage = options?.Storage ?? new LocalStorage<object>();
Credentials = options?.Credentials;
Plugins = options?.Plugins ?? [];
OAuth = options?.OAuth ?? new OAuthSettings();
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
cbd96f9 to
3e8c7b1
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Add Startup section to CLAUDE.md with knowledge file loading, session context, private notes support, and quick-start commands - Add Lessons Learned section to CLAUDE.md for persistent knowledge - Create Claude-KB.md for cross-session learning - Add session-context.md and *-private.md to .gitignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce CloudEnvironment class that bundles all cloud-specific service endpoints, with predefined instances for Public, USGov (GCCH), USGovDoD, and China (21Vianet). Thread the cloud environment through ClientCredentials, token clients, validation settings, and DI host builders so that all previously hardcoded endpoints are now configurable per cloud. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allow users to override specific CloudEnvironment endpoints (e.g. LoginEndpoint, LoginTenant) via appsettings.json, enabling scenarios like China single-tenant bots that require a tenant-specific login URL. - Add CloudEnvironment.WithOverrides() for layering nullable overrides - Add 8 endpoint override properties + ResolveCloud() helper to TeamsSettings - Unify cloud resolution across Apply(), AddTeamsCore(), and AddTeamsTokenAuthentication() - Add WithOverrides unit tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ated files - Keep static BotTokenClient.BotScope unchanged (avoids breaking change) - Add ActiveBotScope instance property for per-cloud scope configuration - Remove CLAUDE.md, Claude-KB.md, and .gitignore session file entries that were unrelated to sovereign cloud support Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CloudEnvironmentTests: ClientCredentials cloud property defaults and assignment - BotTokenClientTests: ActiveBotScope defaults, overrides, and usage in GetAsync - TeamsValidationSettingsTests: sovereign cloud issuers, JWKS, login endpoints, tenant-specific URLs, and audience handling Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add TrimEnd('/') on base URL properties in CloudEnvironment constructor
- Add null/whitespace guard to FromName()
- Add IsNullOrWhiteSpace guard to ResolveCloud()
- Add copyright header to TeamsValidationSettings.cs
- Sync ClientCredentials.Cloud when credentials provided programmatically
- Add AppBuilder.AddCloud() and use in AddTeamsCore AppBuilder overload
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
834ec60 to
600f5da
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Review: Missing Graph scope in CloudEnvironment + merge recommendationMissing GraphScope propertyThe CloudEnvironment class bundles 8 cloud-specific URLs but is missing the Microsoft Graph token scope, which varies per sovereign cloud. The current code in BotTokenClient still hardcodes Microsoft docs state:
Source: Access national cloud deployments with the Microsoft Graph SDKs Correct Graph scopes per cloud (from Microsoft Graph national cloud deployments):
Note: DoD uses a different Graph host than GCC High ( Suggested fix: Add a GraphScope property to CloudEnvironment, set it correctly in all 4 presets, and update BotTokenClient to use it. Merge path recommendationThis PR (#352) is the Libraries-layer foundation that #413 (next/core) depends on. Since the next version ships from core/:
The CloudEnvironment approach in this PR is the correct pattern — it works with or without MSAL, and covers the 6 URL categories that MSAL does not handle (bot scope, graph scope, token service, OpenID metadata, token issuer, channel service). |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
CloudEnvironmentclass (Microsoft.Teams.Api.Auth) bundling all cloud-specific service endpoints with predefined static instances:Public,USGov(GCCH),USGovDoD, andChina(21Vianet)ClientCredentials,BotTokenClient,UserTokenClient,BotSignInClient,App,TeamsValidationSettings, and DI host builders so all previously hardcoded endpoints are configurable per cloudCloudproperty toAppOptions(programmatic) andTeamsSettings(appsettings.json) for easy configurationTeamsSettingsfor scenarios requiring per-endpoint customizationGraphScopetoCloudEnvironmentfor cloud-aware Microsoft Graph token acquisitionActiveBotScopeandActiveGraphScopeinstance properties onBotTokenClient(staticBotScopeandGraphScopepreserved for backwards compatibility)AppBuilder.AddCloud()for the AppBuilder host builder pathClientCredentials.Cloudwhen credentials are provided programmaticallyTrimEnd('/')on base URLs, null/whitespace guards onFromName()andResolveCloud()Note
graphBaseUrl(Graph API endpoint per cloud) is intentionally deferred. This PR focuses on auth/token acquisition. Graph API routing is a separate concern and the Graph client already accepts abaseUrlRootoverride.Sources
Test plan
dotnet build-- 0 errorsdotnet test-- 625 tests passCloud: USGovagainst real GCCH tenant -- JWT validated, echo reply sent🤖 Generated with Claude Code