microsoft/teams.net

Public

mirrored fromhttps://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
next/core-claude-agents

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

core/samples/PABot/InitCompatAdapter.cs

300lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using Microsoft.AspNetCore.Authentication;
5using Microsoft.Extensions.Options;
6using Microsoft.Identity.Abstractions;
7using Microsoft.Identity.Web;
8using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
9using Microsoft.IdentityModel.JsonWebTokens;
10using Microsoft.Teams.Bot.Core;
11using Microsoft.Teams.Bot.Core.Hosting;
12
13namespace PABot
14{
15 internal static class InitCompatAdapter
16 {
17 private const string DefaultScope = "https://api.botframework.com/.default";
18 private const string AdapterKeyName = "BotAdapter";
19
20 /// <summary>
21 /// Configuration values for MSAL identity (bot or agent).
22 /// </summary>
23 private sealed record MsalIdentityConfig
24 {
25 public required IConfigurationSection ConfigSection { get; init; }
26 public required string ClientId { get; init; }
27 public required string TenantId { get; init; }
28 public required string Scope { get; init; }
29 public required string Instance { get; init; }
30 }
31
32 /// <summary>
33 /// Configuration values for a bot adapter.
34 /// </summary>
35 private sealed record AdapterConfig
36 {
37 public MsalIdentityConfig? BotIdentity { get; init; }
38 public MsalIdentityConfig? AgentIdentity { get; init; }
39 }
40
41 public static IServiceCollection AddTeamsBotApplications(this IServiceCollection services)
42 {
43 // Register shared services (needed once for all adapters)
44 services.AddHttpClient();
45 services.AddTokenAcquisition(true);
46 services.AddInMemoryTokenCaches();
47 services.AddAgentIdentities();
48 services.AddHttpContextAccessor();
49
50 // Register adapter using standard MsalBot/MsalAgent configuration
51 RegisterTeamsBotApplication(services);
52
53 return services;
54 }
55
56 private static void RegisterTeamsBotApplication(IServiceCollection services)
57 {
58 // Read configuration for this adapter
59 AdapterConfig config = ReadAdapterConfig(services);
60
61 // Set up token validation (authentication schemes and authorization policy)
62 ConfigureTokenValidation(services, config);
63
64 // Register MSAL options for token acquisition
65 ConfigureMsalOptions(services, config);
66
67 // Register the routed token acquisition service
68 RegisterRoutedTokenService(services, config);
69
70 // Register HTTP clients with auth handlers
71 RegisterHttpClients(services, config);
72
73 // Register Bot Framework clients
74 RegisterBotClients(services, config);
75 }
76
77 private static AdapterConfig ReadAdapterConfig(IServiceCollection services)
78 {
79 IConfiguration configuration = services.BuildServiceProvider()
80 .GetRequiredService<IConfiguration>();
81
82 IConfigurationSection msalBotSection = configuration.GetSection("MsalBot");
83 IConfigurationSection msalAgentSection = configuration.GetSection("MsalAgent");
84
85 // Read bot identity configuration if provided
86 MsalIdentityConfig? botIdentity = null;
87 string? botClientId = msalBotSection["ClientId"];
88 if (!string.IsNullOrEmpty(botClientId))
89 {
90 botIdentity = new MsalIdentityConfig
91 {
92 ConfigSection = msalBotSection,
93 ClientId = botClientId,
94 TenantId = msalBotSection["TenantId"] ?? string.Empty,
95 Scope = msalBotSection["Scope"] ?? DefaultScope,
96 Instance = msalBotSection["Instance"] ?? "https://login.microsoftonline.com/"
97 };
98 }
99
100 // Read agent identity configuration if provided
101 MsalIdentityConfig? agentIdentity = null;
102 string? agentClientId = msalAgentSection["ClientId"];
103 if (!string.IsNullOrEmpty(agentClientId))
104 {
105 agentIdentity = new MsalIdentityConfig
106 {
107 ConfigSection = msalAgentSection,
108 ClientId = agentClientId,
109 TenantId = msalAgentSection["TenantId"] ?? string.Empty,
110 Scope = msalAgentSection["Scope"] ?? DefaultScope,
111 Instance = msalAgentSection["Instance"] ?? botIdentity?.Instance ?? "https://login.microsoftonline.com/"
112 };
113 }
114
115 // At least one identity must be configured
116 if (botIdentity is null && agentIdentity is null)
117 {
118 throw new InvalidOperationException("At least one identity (MsalBot or MsalAgent) must be configured with a ClientId");
119 }
120
121 return new AdapterConfig
122 {
123 BotIdentity = botIdentity,
124 AgentIdentity = agentIdentity
125 };
126 }
127
128 private static void ConfigureTokenValidation(IServiceCollection services, AdapterConfig config)
129 {
130 // This demonstrates an edge case scenario where two token validation schemes are registered
131 // with different audiences (client IDs). The authorization policy will succeed if EITHER
132 // scheme validates successfully - only one token needs to pass, not both.
133 // Use case: When a bot is also registered as an agentic application and needs to accept
134 // tokens from both the bot registration AND the agentic application registration.
135
136 AuthenticationBuilder authBuilder = services.AddAuthentication();
137
138 // Configure authentication schemes for bot identity if present
139 string? botScheme = null;
140 if (config.BotIdentity is not null)
141 {
142 botScheme = "MsalBot";
143 authBuilder.AddBotAuthentication(config.BotIdentity.ClientId, config.BotIdentity.TenantId, botScheme);
144 }
145
146 // Configure authentication schemes for agent identity if present
147 string? agentScheme = null;
148 if (config.AgentIdentity is not null)
149 {
150 agentScheme = "MsalAgent";
151 authBuilder.AddBotAuthentication(config.AgentIdentity.ClientId, config.AgentIdentity.TenantId, agentScheme);
152 }
153
154 // Create policy scheme that routes based on token audience
155 authBuilder.AddPolicyScheme(AdapterKeyName, AdapterKeyName, options =>
156 {
157 options.ForwardDefaultSelector = context =>
158 SelectAuthenticationScheme(context, config, botScheme, agentScheme);
159 });
160
161 // Create authorization policy
162 services.AddAuthorizationBuilder()
163 .AddPolicy(AdapterKeyName, policy =>
164 {
165 policy.AuthenticationSchemes.Add(AdapterKeyName);
166 policy.RequireAuthenticatedUser();
167 });
168 }
169
170 private static string SelectAuthenticationScheme(
171 HttpContext context,
172 AdapterConfig config,
173 string? botScheme,
174 string? agentScheme)
175 {
176 // Default to first available scheme
177 string defaultScheme = botScheme ?? agentScheme ?? throw new InvalidOperationException("No authentication scheme configured");
178
179 string? authHeader = context.Request.Headers.Authorization.ToString();
180 if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
181 {
182 return defaultScheme;
183 }
184
185 try
186 {
187 string token = authHeader["Bearer ".Length..].Trim();
188 JsonWebToken jwt = new(token);
189 string? audience = jwt.GetClaim("aud")?.Value;
190
191 // Check bot identity
192 if (config.BotIdentity is not null && botScheme is not null &&
193 (audience == config.BotIdentity.ClientId || audience == $"api://{config.BotIdentity.ClientId}"))
194 {
195 return botScheme;
196 }
197
198 // Check agent identity
199 if (config.AgentIdentity is not null && agentScheme is not null &&
200 (audience == config.AgentIdentity.ClientId || audience == $"api://{config.AgentIdentity.ClientId}"))
201 {
202 return agentScheme;
203 }
204 }
205 catch
206 {
207 // If token parsing fails, default to first available scheme
208 }
209
210 return defaultScheme;
211 }
212
213 private static void ConfigureMsalOptions(IServiceCollection services, AdapterConfig config)
214 {
215 // Configure MSAL options for bot identity if present - bind directly from MsalBot configuration section
216 if (config.BotIdentity is not null)
217 {
218 services.Configure<MicrosoftIdentityApplicationOptions>("MsalBot", config.BotIdentity.ConfigSection);
219 }
220
221 // Configure MSAL options for agent identity if present - bind directly from MsalAgent configuration section
222 if (config.AgentIdentity is not null)
223 {
224 services.Configure<MicrosoftIdentityApplicationOptions>("MsalAgent", config.AgentIdentity.ConfigSection);
225 }
226 }
227
228 private static void RegisterRoutedTokenService(IServiceCollection services, AdapterConfig config)
229 {
230 services.AddSingleton<IRoutedTokenAcquisitionService>(sp =>
231 {
232 return new RoutedTokenAcquisitionService(
233 config.BotIdentity is not null,
234 config.AgentIdentity is not null,
235 sp.GetRequiredService<IAuthorizationHeaderProvider>(),
236 sp.GetRequiredService<ILogger<RoutedTokenAcquisitionService>>());
237 });
238 }
239
240 private static void RegisterHttpClients(IServiceCollection services, AdapterConfig config)
241 {
242 services.AddHttpClient("ConversationClient")
243 .AddHttpMessageHandler(sp => CreatePACustomAuthHandler(sp, config));
244
245 services.AddHttpClient("UserTokenClient")
246 .AddHttpMessageHandler(sp => CreatePACustomAuthHandler(sp, config));
247
248 services.AddHttpClient("TeamsApiClient")
249 .AddHttpMessageHandler(sp => CreatePACustomAuthHandler(sp, config));
250 }
251
252 private static void RegisterBotClients(IServiceCollection services, AdapterConfig config)
253 {
254 // Register ConversationClient
255 services.AddSingleton<ConversationClient>(sp =>
256 {
257 HttpClient httpClient = sp.GetRequiredService<IHttpClientFactory>()
258 .CreateClient("ConversationClient");
259 return new ConversationClient(httpClient, sp.GetRequiredService<ILogger<ConversationClient>>());
260 });
261
262 // Register UserTokenClient
263 services.AddSingleton<UserTokenClient>(sp =>
264 {
265 HttpClient httpClient = sp.GetRequiredService<IHttpClientFactory>()
266 .CreateClient("UserTokenClient");
267 return new UserTokenClient(
268 httpClient,
269 sp.GetRequiredService<IConfiguration>(),
270 sp.GetRequiredService<ILogger<UserTokenClient>>());
271 });
272
273
274 // Register TeamsBotApplication
275 services.AddSingleton<BotApplication>(sp =>
276 {
277 return new BotApplication(
278 sp.GetRequiredService<ConversationClient>(),
279 sp.GetRequiredService<UserTokenClient>(),
280 sp.GetRequiredService<ILogger<BotApplication>>()
281 );
282 });
283 }
284
285 private static DelegatingHandler CreatePACustomAuthHandler(IServiceProvider sp, AdapterConfig config)
286 {
287 // Use bot scope if available, otherwise use agent scope
288 string? botScope = config.BotIdentity?.Scope;
289 string? agentScope = config.AgentIdentity?.Scope;
290
291 return new PACustomAuthHandler(
292 sp.GetRequiredService<IAuthorizationHeaderProvider>(),
293 sp.GetRequiredService<IRoutedTokenAcquisitionService>(),
294 sp.GetRequiredService<ILogger<PACustomAuthHandler>>(),
295 botScope ?? agentScope ?? DefaultScope,
296 agentScope,
297 sp.GetService<IOptions<ManagedIdentityOptions>>());
298 }
299 }
300}
301