Skip to content

Add sovereign cloud support (GCCH, DoD, China)#352

Open
rajan-chari wants to merge 8 commits intomainfrom
feature/sovereign-cloud-support
Open

Add sovereign cloud support (GCCH, DoD, China)#352
rajan-chari wants to merge 8 commits intomainfrom
feature/sovereign-cloud-support

Conversation

@rajan-chari
Copy link
Copy Markdown
Contributor

@rajan-chari rajan-chari commented Feb 26, 2026

Summary

  • Introduces CloudEnvironment class (Microsoft.Teams.Api.Auth) bundling all cloud-specific service endpoints with predefined static instances: Public, USGov (GCCH), USGovDoD, and China (21Vianet)
  • Threads cloud environment through ClientCredentials, BotTokenClient, UserTokenClient, BotSignInClient, App, TeamsValidationSettings, and DI host builders so all previously hardcoded endpoints are configurable per cloud
  • Adds Cloud property to AppOptions (programmatic) and TeamsSettings (appsettings.json) for easy configuration
  • Adds individual endpoint override properties to TeamsSettings (e.g. LoginEndpoint, LoginTenant) for scenarios requiring per-endpoint customization — such as China single-tenant bots that need a tenant-specific login URL

Usage

Named cloud preset (appsettings.json):

{
  "Teams": {
    "ClientId": "...",
    "ClientSecret": "...",
    "Cloud": "USGov"
  }
}

Per-endpoint overrides (appsettings.json):

{
  "Teams": {
    "ClientId": "...",
    "ClientSecret": "...",
    "TenantId": "your-tenant",
    "Cloud": "China",
    "LoginTenant": "your-tenant-id"
  }
}

Programmatic:

var app = new App(new AppOptions
{
    Cloud = CloudEnvironment.USGov,
    Credentials = new ClientCredentials("id", "secret")
});

// Or with per-endpoint overrides:
var cloud = CloudEnvironment.China.WithOverrides(loginTenant: "my-tenant-id");

Valid cloud names: Public (default), USGov, USGovDoD, China

Override properties: LoginEndpoint, LoginTenant, BotScope, TokenServiceUrl, OpenIdMetadataUrl, TokenIssuer, ChannelService, OAuthRedirectUrl

Files changed

Action File
New Libraries/Microsoft.Teams.Api/Auth/CloudEnvironment.cs
New Tests/Microsoft.Teams.Api.Tests/Auth/CloudEnvironmentTests.cs
Modified Libraries/Microsoft.Teams.Api/Auth/ClientCredentials.cs
Modified Libraries/Microsoft.Teams.Api/Clients/BotTokenClient.cs
Modified Libraries/Microsoft.Teams.Api/Clients/UserTokenClient.cs
Modified Libraries/Microsoft.Teams.Api/Clients/BotSignInClient.cs
Modified Libraries/Microsoft.Teams.Apps/AppOptions.cs
Modified Libraries/Microsoft.Teams.Apps/App.cs
Modified TeamsSettings.cs (Extensions.Configuration) — endpoint override properties + ResolveCloud()
Modified HostApplicationBuilder.cs (Extensions.Hosting) — unified cloud resolution via ResolveCloud()
Modified HostApplicationBuilder.cs (Plugins.AspNetCore) — unified cloud resolution via ResolveCloud()
Modified TeamsValidationSettings.cs (Plugins.AspNetCore)

Test plan

  • dotnet build — 0 errors
  • dotnet test — all existing + new tests pass
  • CloudEnvironment.Public URLs match current hardcoded values (backward compat)
  • WithOverrides() with all nulls returns same instance (no allocation)
  • Individual and multiple endpoint overrides replace correct properties while preserving others
  • Verify CloudEnvironment.USGov URLs match BF GCCH docs
  • Verify CloudEnvironment.China URLs match BF China docs
  • Test sample app (Samples.Echo) starts correctly with no cloud config (defaults to Public)

🤖 Generated with Claude Code

@rajan-chari
Copy link
Copy Markdown
Contributor Author

Manual Test Results

Tested locally by linking a scaffolded C# echo bot to this branch via ProjectReference (all 7 SDK library projects).

Environment

  • Windows 11, .NET 9, PowerShell 7
  • Bot registered in Azure with Teams channel + DevTunnel
  • SDK linked from local checkout of this branch

Tests

# Test Result
1 Negative test — invalid cloud name Pass. Set "Cloud": "Narnia" in appsettings.json under Teams. Bot crashed on startup with: ArgumentException: Unknown cloud environment: 'Narnia'. Valid values are: Public, USGov, USGovDoD, China. — clear, actionable error message.
2 Default behavior (no Cloud key) Pass. Without "Cloud" in config, bot starts normally and echoes messages in Teams. Defaults to CloudEnvironment.Public as expected.
3 Config-driven preset Pass. Setting "Cloud": "Public" in config starts the bot normally — CloudEnvironment.FromName resolves correctly.

Observation

The config path (appsettings.jsonTeamsSettings.Cloud) supports only the 4 named presets. Custom endpoints (arbitrary LoginEndpoint, TokenServiceUrl, etc.) are only available via the code path (new CloudEnvironment(...)). This seems fine for the target scenario but worth noting.

Tested with teams-sdk-dev tooling.

@rajan-chari
Copy link
Copy Markdown
Contributor Author

Updated Test Results (after e0b56f6 — individual endpoint overrides)

Re-tested with the new WithOverrides / ResolveCloud / per-endpoint config support.

Tests

# 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 ResolveCloudWithOverrides.
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

  • WithOverrides returns this when all overrides are null — nice zero-allocation fast path.
  • ResolveCloud chains cleanly: programmatic AppOptions.Cloud → config "Cloud" preset → Public fallback → per-endpoint overrides. Priority order is clear.
  • All 8 endpoint properties are individually overridable from appsettings.json under the Teams section.

@rajan-chari
Copy link
Copy Markdown
Contributor Author

URL Verification: CloudEnvironment presets vs Bot Framework docs

Cross-referenced all CloudEnvironment preset URLs against the official Bot Framework sovereign cloud docs.

USGov (GCCH)

Property PR value BF Docs value Match
LoginEndpoint https://login.microsoftonline.us https://login.microsoftonline.us Yes
LoginTenant MicrosoftServices.onmicrosoft.us MicrosoftServices.onmicrosoft.us Yes
BotScope https://api.botframework.us/.default https://api.botframework.us Yes*
TokenServiceUrl https://tokengcch.botframework.azure.us https://tokengcch.botframework.azure.us/ Yes*
OpenIdMetadataUrl https://login.botframework.azure.us/v1/.well-known/openidconfiguration https://login.botframework.azure.us/v1/.well-known/openidconfiguration Yes
TokenIssuer https://api.botframework.us https://api.botframework.us Yes
ChannelService https://botframework.azure.us https://botframework.azure.us Yes
OAuthRedirectUrl https://tokengcch.botframework.azure.us/.auth/web/redirect https://tokengcch.botframework.azure.us/.auth/web/redirect Yes

USGovDoD

Property PR value BF Docs value Match
LoginEndpoint https://login.microsoftonline.us https://login.microsoftonline.us Yes
LoginTenant MicrosoftServices.onmicrosoft.us MicrosoftServices.onmicrosoft.us Yes
BotScope https://api.botframework.us/.default https://api.botframework.us Yes*
TokenServiceUrl https://apiDoD.botframework.azure.us https://apiDoD.botframework.azure.us Yes
OpenIdMetadataUrl https://login.botframework.azure.us/v1/.well-known/openidconfiguration https://login.botframework.azure.us/v1/.well-known/openidconfiguration Yes
TokenIssuer https://api.botframework.us https://api.botframework.us Yes
ChannelService https://botframework.azure.us https://botframework.azure.us Yes
OAuthRedirectUrl https://apiDoD.botframework.azure.us/.auth/web/redirect (not in docs, consistent with OAuthUrl pattern) Yes

Notes on the two * items

  • BotScope /.default suffix: The PR uses https://api.botframework.us/.default — this is the standard MSAL/OAuth2 scope format. The docs show the resource identifier (https://api.botframework.us), but /.default is the correct programmatic form. This is intentional and correct.
  • TokenServiceUrl trailing slash: Docs show a trailing / on GCCH (https://tokengcch.botframework.azure.us/), PR omits it. Functionally equivalent.

All URLs match. Source: Deploy Bots to Azure Government and Office 365 GCC High

Copy link
Copy Markdown
Member

@rido-min rido-min left a comment

Choose a reason for hiding this comment

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

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:

  1. 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.

  1. 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.

  1. 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?

@rido-min
Copy link
Copy Markdown
Member

  • 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

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)

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 27, 2026

@rido-min I've opened a new pull request, #357, to work on those changes. Once the pull request is ready, I'll request review from you.

rajan-chari and others added 5 commits April 3, 2026 13:17
- 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>
@corinagum corinagum force-pushed the feature/sovereign-cloud-support branch from 96871c5 to cbd96f9 Compare April 6, 2026 21:25
@corinagum corinagum marked this pull request as ready for review April 6, 2026 21:25
Copilot AI review requested due to automatic review settings April 6, 2026 21:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 CloudEnvironment presets (Public, USGov, USGovDoD, China) plus FromName() and WithOverrides() 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.Cloud is applied to the API clients (bot scope + token service URLs), but it isn’t applied to ClientCredentials. In the programmatic usage shown in the PR description (Cloud = CloudEnvironment.USGov + Credentials = new ClientCredentials(...)), ClientCredentials.Cloud will remain Public and token acquisition will still hit login.microsoftonline.com. Consider propagating cloud into ClientCredentials when Credentials is a ClientCredentials instance (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.

@corinagum corinagum force-pushed the feature/sovereign-cloud-support branch from cbd96f9 to 3e8c7b1 Compare April 6, 2026 22:21
Copilot AI review requested due to automatic review settings April 7, 2026 21:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment on lines +54 to +55
var cloud = options?.Cloud ?? CloudEnvironment.Public;

Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

App picks the cloud from options.Cloud only. If a caller sets ClientCredentials.Cloud but forgets to also set AppOptions.Cloud, the app will configure Api.Bots.Token.ActiveBotScope/token service URLs for the public cloud while ClientCredentials.Resolve() uses the sovereign login endpoint/tenant, leading to inconsistent token acquisition and hard-to-diagnose auth failures. Consider deriving cloud from options.Credentials when it is a ClientCredentials (and options.Cloud is null), and/or enforcing that options.Cloud and ClientCredentials.Cloud stay in sync (e.g., set one from the other).

Suggested change
var cloud = options?.Cloud ?? CloudEnvironment.Public;
var clientCredentials = options?.Credentials as ClientCredentials;
var optionsCloud = options?.Cloud;
var credentialsCloud = clientCredentials?.Cloud;
if (optionsCloud is not null && credentialsCloud is not null && optionsCloud != credentialsCloud)
{
throw new ArgumentException("AppOptions.Cloud must match ClientCredentials.Cloud when both are specified.", nameof(options));
}
var cloud = optionsCloud ?? credentialsCloud ?? CloudEnvironment.Public;

Copilot uses AI. Check for mistakes.
public async Task<ITokenResponse> Resolve(IHttpClient client, string[] scopes, CancellationToken cancellationToken = default)
{
var tenantId = TenantId ?? "botframework.com";
var tenantId = TenantId ?? Cloud.LoginTenant;
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

TenantId ?? Cloud.LoginTenant treats an empty/whitespace TenantId as a real tenant value, producing invalid token URLs like ...//oauth2/v2.0/token. Consider using string.IsNullOrWhiteSpace(TenantId) to fall back to Cloud.LoginTenant (and similarly treating whitespace-only values as unset).

Suggested change
var tenantId = TenantId ?? Cloud.LoginTenant;
var tenantId = string.IsNullOrWhiteSpace(TenantId) ? Cloud.LoginTenant : TenantId;

Copilot uses AI. Check for mistakes.
public string GetTenantSpecificOpenIdMetadataUrl(string? tenantId)
{
return $"https://login.microsoftonline.com/{tenantId ?? "common"}/v2.0/.well-known/openid-configuration";
return $"{LoginEndpoint}/{tenantId ?? "common"}/v2.0/.well-known/openid-configuration";
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

GetTenantSpecificOpenIdMetadataUrl uses tenantId ?? "common", so callers passing an empty string (or whitespace) will generate an invalid URL segment instead of defaulting to common. Consider normalizing with string.IsNullOrWhiteSpace(tenantId) and defaulting to common in that case.

Suggested change
return $"{LoginEndpoint}/{tenantId ?? "common"}/v2.0/.well-known/openid-configuration";
var normalizedTenantId = string.IsNullOrWhiteSpace(tenantId) ? "common" : tenantId;
return $"{LoginEndpoint}/{normalizedTenantId}/v2.0/.well-known/openid-configuration";

Copilot uses AI. Check for mistakes.
@@ -29,13 +46,13 @@ public IEnumerable<string> GetValidIssuersForTenant(string? tenantId)
var validIssuers = new List<string>();
if (!string.IsNullOrEmpty(tenantId))
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

GetValidIssuersForTenant only checks string.IsNullOrEmpty(tenantId), so whitespace tenant IDs will still be used to build issuer URLs (and could create invalid issuers). Consider using string.IsNullOrWhiteSpace(tenantId) for the guard.

Suggested change
if (!string.IsNullOrEmpty(tenantId))
if (!string.IsNullOrWhiteSpace(tenantId))

Copilot uses AI. Check for mistakes.
Comment on lines 42 to 45
var token = cancellationToken != default ? cancellationToken : _cancellationToken;
var query = QueryString.Serialize(request);
var req = HttpRequest.Get($"https://token.botframework.com/api/usertoken/GetToken?{query}");
var req = HttpRequest.Get($"{TokenServiceUrl}/api/usertoken/GetToken?{query}");
var res = await _http.SendAsync<Token.Response>(req, token);
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

TokenServiceUrl is concatenated into request URLs without normalization. If TokenServiceUrl is configured with a trailing slash, this produces a //api/... path segment. Consider trimming trailing slashes in the setter or before composing the request URL.

Copilot uses AI. Check for mistakes.
Comment on lines 34 to 38
var token = cancellationToken != default ? cancellationToken : _cancellationToken;
var query = QueryString.Serialize(request);
var req = HttpRequest.Get(
$"https://token.botframework.com/api/botsignin/GetSignInUrl?{query}"
$"{TokenServiceUrl}/api/botsignin/GetSignInUrl?{query}"
);
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

TokenServiceUrl is concatenated into request URLs without normalization. If a consumer sets TokenServiceUrl with a trailing slash, the resulting request path will contain //api/.... Consider trimming trailing slashes in the setter or when building the request URL.

Copilot uses AI. Check for mistakes.
Comment on lines +68 to 82
// cloud environment (base preset + per-endpoint overrides)
var cloud = settings.ResolveCloud();

// client credentials
if (settings.ClientId is not null && settings.ClientSecret is not null && !settings.Empty)
{
appBuilder = appBuilder.AddCredentials(new ClientCredentials(
var credentials = new ClientCredentials(
settings.ClientId,
settings.ClientSecret,
settings.TenantId
));
)
{ Cloud = cloud };

appBuilder = appBuilder.AddCredentials(credentials);
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

In the AddTeamsCore(IHostApplicationBuilder, AppBuilder) overload, the resolved cloud is not applied to the AppBuilder/AppOptions (only ClientCredentials.Cloud is set). This means App will still default to CloudEnvironment.Public and configure Api.Bots.Token.ActiveBotScope/token service URLs for public cloud, creating a mismatch with the sovereign ClientCredentials.Cloud. Consider threading the resolved cloud into the AppBuilder options (e.g., add an AddCloud(CloudEnvironment) builder API, or build an AppOptions with Cloud = cloud and use the AddTeamsCore(builder, AppOptions) overload) so config-based cloud selection works in this overload.

Copilot uses AI. Check for mistakes.
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.

5 participants