microsoft/teams.net

Public

mirrored from https://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
aamirj/umi

Branches

Tags

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

Clone

HTTPS

Download ZIP

core/src/Microsoft.Bot.Core/Hosting/BotAuthenticationHandler.cs

177lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.Net.Http.Headers;
5
6using Microsoft.Bot.Core.Schema;
7using Microsoft.Extensions.Logging;
8using Microsoft.Identity.Abstractions;
9using Microsoft.Identity.Web;
10
11namespace Microsoft.Bot.Core.Hosting;
12
13
14/// <summary>
15/// Represents an agentic identity for user-delegated token acquisition.
16/// </summary>
17internal sealed class AgenticIdentity
18{
19 public string? AgenticAppId { get; set; }
20 public string? AgenticUserId { get; set; }
21 public string? AgenticAppBlueprintId { get; set; }
22
23 public static AgenticIdentity? FromProperties(ExtendedPropertiesDictionary? properties)
24 {
25 if (properties is null)
26 {
27 return null;
28 }
29
30 properties.TryGetValue("agenticAppId", out object? appIdObj);
31 properties.TryGetValue("agenticUserId", out object? userIdObj);
32 properties.TryGetValue("agenticAppBlueprintId", out object? bluePrintObj);
33 return new AgenticIdentity
34 {
35 AgenticAppId = appIdObj?.ToString(),
36 AgenticUserId = userIdObj?.ToString(),
37 AgenticAppBlueprintId = bluePrintObj?.ToString()
38 };
39 }
40}
41
42/// <summary>
43/// HTTP message handler that automatically acquires and attaches authentication tokens
44/// for Bot Framework API calls. Supports both app-only and agentic (user-delegated) token acquisition.
45/// </summary>
46/// <remarks>
47/// Initializes a new instance of the <see cref="BotAuthenticationHandler"/> class.
48/// </remarks>
49/// <param name="authorizationHeaderProvider">The authorization header provider for acquiring tokens.</param>
50/// <param name="logger">The logger instance.</param>
51/// <param name="scope">The scope for the token request.</param>
52/// <param name="aadConfigSectionName">The configuration section name for Azure AD settings.</param>
53internal sealed class BotAuthenticationHandler(
54 IAuthorizationHeaderProvider authorizationHeaderProvider,
55 ILogger<BotAuthenticationHandler> logger,
56 string scope,
57 string aadConfigSectionName = "AzureAd") : DelegatingHandler
58{
59 private readonly IAuthorizationHeaderProvider _authorizationHeaderProvider = authorizationHeaderProvider ?? throw new ArgumentNullException(nameof(authorizationHeaderProvider));
60 private readonly ILogger<BotAuthenticationHandler> _logger = logger ?? throw new ArgumentNullException(nameof(logger));
61 private readonly string _scope = scope ?? throw new ArgumentNullException(nameof(scope));
62 private readonly string _aadConfigSectionName = aadConfigSectionName ?? throw new ArgumentNullException(nameof(aadConfigSectionName));
63
64 private static readonly Action<ILogger, string, string, Exception?> LogAcquiringAgenticToken =
65 LoggerMessage.Define<string, string>(
66 LogLevel.Debug,
67 new EventId(1, nameof(LogAcquiringAgenticToken)),
68 "Acquiring agentic token for appId: {AgenticAppId}, userId: {AgenticUserId}");
69
70 private static readonly Action<ILogger, string, Exception?> LogAcquiringAppOnlyToken =
71 LoggerMessage.Define<string>(
72 LogLevel.Debug,
73 new EventId(2, nameof(LogAcquiringAppOnlyToken)),
74 "Acquiring app-only token for scope: {Scope}");
75
76 /// <summary>
77 /// Key used to store the agentic identity in HttpRequestMessage options.
78 /// </summary>
79 public static readonly HttpRequestOptionsKey<AgenticIdentity?> AgenticIdentityKey = new("AgenticIdentity");
80
81 /// <inheritdoc/>
82 protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
83 {
84 request.Options.TryGetValue(AgenticIdentityKey, out AgenticIdentity? agenticIdentity);
85
86 // TEMPORARY: Hardcoded Managed Identity test
87 const string HARDCODED_MANAGED_IDENTITY_CLIENT_ID = "36cc4d80-a643-49fc-8956-47afc1521748";
88 string token = await GetTokenUsingManagedIdentityAsync(HARDCODED_MANAGED_IDENTITY_CLIENT_ID, cancellationToken).ConfigureAwait(false);
89 // string token = await GetAuthorizationHeaderAsync(agenticIdentity, cancellationToken).ConfigureAwait(false);
90
91 string tokenValue = token.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)
92 ? token["Bearer ".Length..]
93 : token;
94
95 request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenValue);
96
97 return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
98 }
99
100 /// <summary>
101 /// Gets an authorization header for Bot Framework API calls.
102 /// Supports both app-only and agentic (user-delegated) token acquisition.
103 /// </summary>
104 /// <param name="agenticIdentity">Optional agentic identity for user-delegated token acquisition. If not provided, acquires an app-only token.</param>
105 /// <param name="cancellationToken">Cancellation token.</param>
106 /// <returns>The authorization header value.</returns>
107 private async Task<string> GetAuthorizationHeaderAsync(AgenticIdentity? agenticIdentity, CancellationToken cancellationToken)
108 {
109 AuthorizationHeaderProviderOptions options = new()
110 {
111 AcquireTokenOptions = new AcquireTokenOptions()
112 {
113 AuthenticationOptionsName = _aadConfigSectionName,
114 }
115 };
116
117 if (agenticIdentity is not null &&
118 !string.IsNullOrEmpty(agenticIdentity.AgenticAppId) &&
119 !string.IsNullOrEmpty(agenticIdentity.AgenticUserId))
120 {
121 LogAcquiringAgenticToken(_logger, agenticIdentity.AgenticAppId, agenticIdentity.AgenticUserId, null);
122
123 options.WithAgentUserIdentity(agenticIdentity.AgenticAppId, Guid.Parse(agenticIdentity.AgenticUserId));
124 string token = await _authorizationHeaderProvider.CreateAuthorizationHeaderAsync([_scope], options, null, cancellationToken).ConfigureAwait(false);
125 return token;
126 }
127
128 LogAcquiringAppOnlyToken(_logger, _scope, null);
129 string appToken = await _authorizationHeaderProvider.CreateAuthorizationHeaderForAppAsync(_scope, options, cancellationToken).ConfigureAwait(false);
130 return appToken;
131 }
132
133 /// <summary>
134 /// Gets a token using User-Assigned Managed Identity via Microsoft.Identity.Web.
135 /// Based on: https://github.com/AzureAD/microsoft-identity-web/wiki/Calling-APIs-with-Managed-Identity
136 /// </summary>
137 /// <param name="managedIdentityClientId">The Client ID (GUID) of the user-assigned managed identity. Required.</param>
138 /// <param name="cancellationToken">Cancellation token.</param>
139 /// <returns>The authorization header value.</returns>
140 /// <remarks>
141 /// This method uses Microsoft.Identity.Web's built-in managed identity support to acquire tokens
142 /// without requiring ClientId/ClientSecret/TenantId in the AzureAd configuration section.
143 ///
144 /// The managed identity must be assigned to the Azure resource (App Service, VM, etc.) and must have
145 /// the appropriate permissions to access the Bot Framework API.
146 ///
147 /// To use this method, set the UseManagedIdentityKey and ManagedIdentityClientIdKey options on the HttpRequestMessage.
148 /// </remarks>
149 private async Task<string> GetTokenUsingManagedIdentityAsync(string? managedIdentityClientId, CancellationToken cancellationToken)
150 {
151 if (string.IsNullOrEmpty(managedIdentityClientId))
152 {
153 throw new ArgumentException("Managed Identity Client ID is required when using UseManagedIdentityKey.", nameof(managedIdentityClientId));
154 }
155
156 LogAcquiringManagedIdentityToken(_logger, _scope, managedIdentityClientId, null);
157
158 // Configure options with ManagedIdentity settings
159 // This follows the pattern from: https://github.com/AzureAD/microsoft-identity-web/wiki/Calling-APIs-with-Managed-Identity
160 AuthorizationHeaderProviderOptions options = new()
161 {
162 AcquireTokenOptions = new AcquireTokenOptions()
163 {
164 AuthenticationOptionsName = _aadConfigSectionName,
165 ManagedIdentity = new ManagedIdentityOptions
166 {
167 UserAssignedClientId = managedIdentityClientId
168 }
169 }
170 };
171
172 // Use CreateAuthorizationHeaderForAppAsync - Microsoft.Identity.Web will detect the ManagedIdentity option
173 // and use managed identity instead of client credentials
174 string token = await _authorizationHeaderProvider.CreateAuthorizationHeaderForAppAsync(_scope, options, cancellationToken).ConfigureAwait(false);
175 return token;
176 }
177}