microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
samples/migration-bot

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

124lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.IdentityModel.Tokens.Jwt;
5using System.Net.Http.Headers;
6using Microsoft.Extensions.Logging;
7using Microsoft.Extensions.Options;
8using Microsoft.Identity.Abstractions;
9using Microsoft.Identity.Web;
10using Microsoft.Teams.Bot.Core.Schema;
11
12namespace Microsoft.Teams.Bot.Core.Hosting;
13
14/// <summary>
15/// HTTP message handler that automatically acquires and attaches authentication tokens
16/// for Bot Framework API calls. Supports both app-only and agentic (user-delegated) token acquisition.
17/// </summary>
18/// <remarks>
19/// Initializes a new instance of the <see cref="BotAuthenticationHandler"/> class.
20/// </remarks>
21/// <param name="authorizationHeaderProvider">The authorization header provider for acquiring tokens.</param>
22/// <param name="logger">The logger instance.</param>
23/// <param name="scope">The scope for the token request.</param>
24/// <param name="managedIdentityOptions">Optional managed identity options for user-assigned managed identity authentication.</param>
25internal sealed class BotAuthenticationHandler(
26 IAuthorizationHeaderProvider authorizationHeaderProvider,
27 ILogger<BotAuthenticationHandler> logger,
28 string scope,
29 IOptions<ManagedIdentityOptions>? managedIdentityOptions = null) : DelegatingHandler
30{
31 private readonly IAuthorizationHeaderProvider _authorizationHeaderProvider = authorizationHeaderProvider ?? throw new ArgumentNullException(nameof(authorizationHeaderProvider));
32 private readonly ILogger<BotAuthenticationHandler> _logger = logger ?? throw new ArgumentNullException(nameof(logger));
33 private readonly string _scope = scope ?? throw new ArgumentNullException(nameof(scope));
34 private readonly IOptions<ManagedIdentityOptions>? _managedIdentityOptions = managedIdentityOptions;
35 private static readonly Action<ILogger, string, Exception?> _logAgenticToken =
36 LoggerMessage.Define<string>(LogLevel.Information, new(2), "Acquiring agentic token for AgenticAppId {AgenticAppId}");
37 private static readonly Action<ILogger, string, Exception?> _logAppOnlyToken =
38 LoggerMessage.Define<string>(LogLevel.Information, new(3), "Acquiring app-only token for scope: {Scope}");
39 private static readonly Action<ILogger, string, Exception?> _logTokenClaims =
40 LoggerMessage.Define<string>(LogLevel.Trace, new(4), "Acquired token claims:{Claims}");
41
42 /// <summary>
43 /// Key used to store the agentic identity in HttpRequestMessage options.
44 /// </summary>
45 public static readonly HttpRequestOptionsKey<AgenticIdentity?> AgenticIdentityKey = new("AgenticIdentity");
46
47 /// <inheritdoc/>
48 protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
49 {
50 request.Options.TryGetValue(AgenticIdentityKey, out AgenticIdentity? agenticIdentity);
51
52 string token = await GetAuthorizationHeaderAsync(agenticIdentity, cancellationToken).ConfigureAwait(false);
53
54 string tokenValue = token.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)
55 ? token["Bearer ".Length..]
56 : token;
57
58 LogTokenClaims(tokenValue);
59
60 request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenValue);
61
62 return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
63 }
64
65 /// <summary>
66 /// Gets an authorization header for Bot Framework API calls.
67 /// Supports both app-only and agentic (user-delegated) token acquisition.
68 /// </summary>
69 /// <param name="agenticIdentity">Optional agentic identity for user-delegated token acquisition. If not provided, acquires an app-only token.</param>
70 /// <param name="cancellationToken">Cancellation token.</param>
71 /// <returns>The authorization header value.</returns>
72 private async Task<string> GetAuthorizationHeaderAsync(AgenticIdentity? agenticIdentity, CancellationToken cancellationToken)
73 {
74 AuthorizationHeaderProviderOptions options = new()
75 {
76 AcquireTokenOptions = new AcquireTokenOptions()
77 {
78 AuthenticationOptionsName = MsalConfigurationExtensions.MsalConfigKey,
79 }
80 };
81
82 // Conditionally apply ManagedIdentity configuration if registered
83 if (_managedIdentityOptions is not null)
84 {
85 ManagedIdentityOptions miOptions = _managedIdentityOptions.Value;
86
87 if (!string.IsNullOrEmpty(miOptions.UserAssignedClientId))
88 {
89 options.AcquireTokenOptions.ManagedIdentity = miOptions;
90 }
91 }
92
93 if (agenticIdentity is not null &&
94 !string.IsNullOrEmpty(agenticIdentity.AgenticAppId) &&
95 !string.IsNullOrEmpty(agenticIdentity.AgenticUserId))
96 {
97 _logAgenticToken(_logger, agenticIdentity.AgenticAppId, null);
98
99 options.WithAgentUserIdentity(agenticIdentity.AgenticAppId, Guid.Parse(agenticIdentity.AgenticUserId));
100 string token = await _authorizationHeaderProvider.CreateAuthorizationHeaderAsync([_scope], options, null, cancellationToken).ConfigureAwait(false);
101 return token;
102 }
103
104 _logAppOnlyToken(_logger, _scope, null);
105 string appToken = await _authorizationHeaderProvider.CreateAuthorizationHeaderForAppAsync(_scope, options, cancellationToken).ConfigureAwait(false);
106
107
108 return appToken;
109 }
110
111 private void LogTokenClaims(string token)
112 {
113 if (!_logger.IsEnabled(LogLevel.Trace))
114 {
115 return;
116 }
117
118
119 JwtSecurityToken jwtToken = new(token);
120 string claims = Environment.NewLine + string.Join(Environment.NewLine, jwtToken.Claims.Select(c => $" {c.Type}: {c.Value}"));
121 _logTokenClaims(_logger, claims, null);
122
123 }
124}
125