diff --git a/samples/dotnet/RetrievalBot/Agents/RetrievalAgent.cs b/samples/dotnet/RetrievalBot/Agents/RetrievalAgent.cs index eacae3ab..28d23c64 100644 --- a/samples/dotnet/RetrievalBot/Agents/RetrievalAgent.cs +++ b/samples/dotnet/RetrievalBot/Agents/RetrievalAgent.cs @@ -76,7 +76,7 @@ public RetrievalAgent(Kernel kernel , AgentApplication app) /// /// A message to process. /// An instance of - public async Task InvokeAgentAsync(string input, ChatHistory chatHistory) + public async Task InvokeAgentAsync(string input, ChatHistory chatHistory) { ArgumentNullException.ThrowIfNull(chatHistory); diff --git a/samples/dotnet/RetrievalBot/AspNetExtensions.cs b/samples/dotnet/RetrievalBot/AspNetExtensions.cs new file mode 100644 index 00000000..5ffbff7e --- /dev/null +++ b/samples/dotnet/RetrievalBot/AspNetExtensions.cs @@ -0,0 +1,270 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Authentication; +using Microsoft.Agents.Core; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Protocols; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Validators; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +public static class AspNetExtensions +{ + private static readonly ConcurrentDictionary> _openIdMetadataCache = new(); + + /// + /// Adds AspNet token validation typical for ABS/SMBA and agent-to-agent using settings in configuration. + /// + /// + /// + /// Name of the config section to read. + /// Optional logger to use for authentication event logging. + /// + /// This extension reads settings from configuration. If configuration is missing JWT token + /// is not enabled. + ///

The minimum, but typical, configuration is:

+ /// + /// "TokenValidation": { + /// "Enabled": boolean, + /// "Audiences": [ + /// "{{ClientId}}" // this is the Client ID used for the Azure Bot + /// ], + /// "TenantId": "{{TenantId}}" + /// } + /// + /// The full options are: + /// + /// "TokenValidation": { + /// "Enabled": boolean, + /// "Audiences": [ + /// "{required:agent-appid}" + /// ], + /// "TenantId": "{recommended:tenant-id}", + /// "ValidIssuers": [ + /// "{default:Public-AzureBotService}" + /// ], + /// "IsGov": {optional:false}, + /// "AzureBotServiceOpenIdMetadataUrl": optional, + /// "OpenIdMetadataUrl": optional, + /// "AzureBotServiceTokenHandling": "{optional:true}" + /// "OpenIdMetadataRefresh": "optional-12:00:00" + /// } + /// + ///
+ public static void AddAgentAspNetAuthentication(this IServiceCollection services, IConfiguration configuration, string tokenValidationSectionName = "TokenValidation") + { + IConfigurationSection tokenValidationSection = configuration.GetSection(tokenValidationSectionName); + + if (!tokenValidationSection.Exists() || !tokenValidationSection.GetValue("Enabled", true)) + { + // Noop if TokenValidation section missing or disabled. + System.Diagnostics.Trace.WriteLine("AddAgentAspNetAuthentication: Auth disabled"); + return; + } + + services.AddAgentAspNetAuthentication(tokenValidationSection.Get()!); + } + + /// + /// Adds AspNet token validation typical for ABS/SMBA and agent-to-agent. + /// + public static void AddAgentAspNetAuthentication(this IServiceCollection services, TokenValidationOptions validationOptions) + { + AssertionHelpers.ThrowIfNull(validationOptions, nameof(validationOptions)); + + // Must have at least one Audience. + if (validationOptions.Audiences == null || validationOptions.Audiences.Count == 0) + { + throw new ArgumentException($"{nameof(TokenValidationOptions)}:Audiences requires at least one ClientId"); + } + + // Audience values must be GUID's + foreach (var audience in validationOptions.Audiences) + { + if (!Guid.TryParse(audience, out _)) + { + throw new ArgumentException($"{nameof(TokenValidationOptions)}:Audiences values must be a GUID"); + } + } + + // If ValidIssuers is empty, default for ABS Public Cloud + if (validationOptions.ValidIssuers == null || validationOptions.ValidIssuers.Count == 0) + { + validationOptions.ValidIssuers = + [ + "https://api.botframework.com", + "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", + "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0", + "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", + "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", + "https://sts.windows.net/69e9b82d-4842-4902-8d1e-abc5b98a55e8/", + "https://login.microsoftonline.com/69e9b82d-4842-4902-8d1e-abc5b98a55e8/v2.0", + ]; + + if (!string.IsNullOrEmpty(validationOptions.TenantId) && Guid.TryParse(validationOptions.TenantId, out _)) + { + validationOptions.ValidIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV1, validationOptions.TenantId)); + validationOptions.ValidIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV2, validationOptions.TenantId)); + } + } + + // If the `AzureBotServiceOpenIdMetadataUrl` setting is not specified, use the default based on `IsGov`. This is what is used to authenticate ABS tokens. + if (string.IsNullOrEmpty(validationOptions.AzureBotServiceOpenIdMetadataUrl)) + { + validationOptions.AzureBotServiceOpenIdMetadataUrl = validationOptions.IsGov ? AuthenticationConstants.GovAzureBotServiceOpenIdMetadataUrl : AuthenticationConstants.PublicAzureBotServiceOpenIdMetadataUrl; + } + + // If the `OpenIdMetadataUrl` setting is not specified, use the default based on `IsGov`. This is what is used to authenticate Entra ID tokens. + if (string.IsNullOrEmpty(validationOptions.OpenIdMetadataUrl)) + { + validationOptions.OpenIdMetadataUrl = validationOptions.IsGov ? AuthenticationConstants.GovOpenIdMetadataUrl : AuthenticationConstants.PublicOpenIdMetadataUrl; + } + + var openIdMetadataRefresh = validationOptions.OpenIdMetadataRefresh ?? BaseConfigurationManager.DefaultAutomaticRefreshInterval; + + _ = services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + options.SaveToken = true; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ClockSkew = TimeSpan.FromMinutes(5), + ValidIssuers = validationOptions.ValidIssuers, + ValidAudiences = validationOptions.Audiences, + ValidateIssuerSigningKey = true, + RequireSignedTokens = true, + }; + + // Using Microsoft.IdentityModel.Validators + options.TokenValidationParameters.EnableAadSigningKeyIssuerValidation(); + + options.Events = new JwtBearerEvents + { + // Create a ConfigurationManager based on the requestor. This is to handle ABS non-Entra tokens. + OnMessageReceived = async context => + { + string authorizationHeader = context.Request.Headers.Authorization.ToString(); + + if (string.IsNullOrEmpty(authorizationHeader)) + { + // Default to AadTokenValidation handling + context.Options.TokenValidationParameters.ConfigurationManager ??= options.ConfigurationManager as BaseConfigurationManager; + await Task.CompletedTask.ConfigureAwait(false); + return; + } + + string[] parts = authorizationHeader?.Split(' ')!; + if (parts.Length != 2 || parts[0] != "Bearer") + { + // Default to AadTokenValidation handling + context.Options.TokenValidationParameters.ConfigurationManager ??= options.ConfigurationManager as BaseConfigurationManager; + await Task.CompletedTask.ConfigureAwait(false); + return; + } + + JwtSecurityToken token = new(parts[1]); + string issuer = token.Claims.FirstOrDefault(claim => claim.Type == AuthenticationConstants.IssuerClaim)?.Value!; + + if (validationOptions.AzureBotServiceTokenHandling && AuthenticationConstants.BotFrameworkTokenIssuer.Equals(issuer)) + { + // Use the Azure Bot authority for this configuration manager + context.Options.TokenValidationParameters.ConfigurationManager = _openIdMetadataCache.GetOrAdd(validationOptions.AzureBotServiceOpenIdMetadataUrl, key => + { + return new ConfigurationManager(validationOptions.AzureBotServiceOpenIdMetadataUrl, new OpenIdConnectConfigurationRetriever(), new HttpClient()) + { + AutomaticRefreshInterval = openIdMetadataRefresh + }; + }); + } + else + { + context.Options.TokenValidationParameters.ConfigurationManager = _openIdMetadataCache.GetOrAdd(validationOptions.OpenIdMetadataUrl, key => + { + return new ConfigurationManager(validationOptions.OpenIdMetadataUrl, new OpenIdConnectConfigurationRetriever(), new HttpClient()) + { + AutomaticRefreshInterval = openIdMetadataRefresh + }; + }); + } + + await Task.CompletedTask.ConfigureAwait(false); + }, + + OnTokenValidated = context => + { + return Task.CompletedTask; + }, + OnForbidden = context => + { + return Task.CompletedTask; + }, + OnAuthenticationFailed = context => + { + return Task.CompletedTask; + } + }; + }); + } + + public class TokenValidationOptions + { + public IList? Audiences { get; set; } + + /// + /// TenantId of the Azure Bot. Optional but recommended. + /// + public string? TenantId { get; set; } + + /// + /// Additional valid issuers. Optional, in which case the Public Azure Bot Service issuers are used. + /// + public IList? ValidIssuers { get; set; } + + /// + /// Can be omitted, in which case public Azure Bot Service and Azure Cloud metadata urls are used. + /// + public bool IsGov { get; set; } = false; + + /// + /// Azure Bot Service OpenIdMetadataUrl. Optional, in which case default value depends on IsGov. + /// + /// + /// + public string? AzureBotServiceOpenIdMetadataUrl { get; set; } + + /// + /// Entra OpenIdMetadataUrl. Optional, in which case default value depends on IsGov. + /// + /// + /// + public string? OpenIdMetadataUrl { get; set; } + + /// + /// Determines if Azure Bot Service tokens are handled. Defaults to true and should always be true until Azure Bot Service sends Entra ID token. + /// + public bool AzureBotServiceTokenHandling { get; set; } = true; + + /// + /// OpenIdMetadata refresh interval. Defaults to 12 hours. + /// + public TimeSpan? OpenIdMetadataRefresh { get; set; } + } +} diff --git a/samples/dotnet/RetrievalBot/AspNetExtentions.cs b/samples/dotnet/RetrievalBot/AspNetExtentions.cs deleted file mode 100644 index 3458cc84..00000000 --- a/samples/dotnet/RetrievalBot/AspNetExtentions.cs +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.Authentication; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.IdentityModel.Protocols; -using Microsoft.IdentityModel.Protocols.OpenIdConnect; -using Microsoft.IdentityModel.Tokens; -using Microsoft.IdentityModel.Validators; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Microsoft.Agents.Samples; - -public static class AspNetExtensions -{ - private static readonly ConcurrentDictionary> _openIdMetadataCache = new(); - - /// - /// Adds token validation typical for ABS/SMBA and agent-to-agent. - /// default to Azure Public Cloud. - /// - /// - /// - /// Name of the config section to read. - /// Optional logger to use for authentication event logging. - /// - /// Configuration: - /// - /// "TokenValidation": { - /// "Audiences": [ - /// "{required:agent-appid}" - /// ], - /// "TenantId": "{recommended:tenant-id}", - /// "ValidIssuers": [ - /// "{default:Public-AzureBotService}" - /// ], - /// "IsGov": {optional:false}, - /// "AzureBotServiceOpenIdMetadataUrl": optional, - /// "OpenIdMetadataUrl": optional, - /// "AzureBotServiceTokenHandling": "{optional:true}" - /// "OpenIdMetadataRefresh": "optional-12:00:00" - /// } - /// - /// - /// `IsGov` can be omitted, in which case public Azure Bot Service and Azure Cloud metadata urls are used. - /// `ValidIssuers` can be omitted, in which case the Public Azure Bot Service issuers are used. - /// `TenantId` can be omitted if the Agent is not being called by another Agent. Otherwise it is used to add other known issuers. Only when `ValidIssuers` is omitted. - /// `AzureBotServiceOpenIdMetadataUrl` can be omitted. In which case default values in combination with `IsGov` is used. - /// `OpenIdMetadataUrl` can be omitted. In which case default values in combination with `IsGov` is used. - /// `AzureBotServiceTokenHandling` defaults to true and should always be true until Azure Bot Service sends Entra ID token. - /// - public static void AddAgentAspNetAuthentication(this IServiceCollection services, IConfiguration configuration, string tokenValidationSectionName = "TokenValidation", ILogger logger = null) - { - IConfigurationSection tokenValidationSection = configuration.GetSection(tokenValidationSectionName); - List validTokenIssuers = tokenValidationSection.GetSection("ValidIssuers").Get>(); - List audiences = tokenValidationSection.GetSection("Audiences").Get>(); - - if (!tokenValidationSection.Exists()) - { - logger?.LogError("Missing configuration section '{tokenValidationSectionName}'. This section is required to be present in appsettings.json", tokenValidationSectionName); - throw new InvalidOperationException($"Missing configuration section '{tokenValidationSectionName}'. This section is required to be present in appsettings.json"); - } - - // If ValidIssuers is empty, default for ABS Public Cloud - if (validTokenIssuers == null || validTokenIssuers.Count == 0) - { - validTokenIssuers = - [ - "https://api.botframework.com", - "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", - "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0", - "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", - "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", - "https://sts.windows.net/69e9b82d-4842-4902-8d1e-abc5b98a55e8/", - "https://login.microsoftonline.com/69e9b82d-4842-4902-8d1e-abc5b98a55e8/v2.0", - ]; - - string tenantId = tokenValidationSection["TenantId"]; - if (!string.IsNullOrEmpty(tenantId)) - { - validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV1, tenantId)); - validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV2, tenantId)); - } - } - - if (audiences == null || audiences.Count == 0) - { - throw new ArgumentException($"{tokenValidationSectionName}:Audiences requires at least one value"); - } - - bool isGov = tokenValidationSection.GetValue("IsGov", false); - bool azureBotServiceTokenHandling = tokenValidationSection.GetValue("AzureBotServiceTokenHandling", true); - - // If the `AzureBotServiceOpenIdMetadataUrl` setting is not specified, use the default based on `IsGov`. This is what is used to authenticate ABS tokens. - string azureBotServiceOpenIdMetadataUrl = tokenValidationSection["AzureBotServiceOpenIdMetadataUrl"]; - if (string.IsNullOrEmpty(azureBotServiceOpenIdMetadataUrl)) - { - azureBotServiceOpenIdMetadataUrl = isGov ? AuthenticationConstants.GovAzureBotServiceOpenIdMetadataUrl : AuthenticationConstants.PublicAzureBotServiceOpenIdMetadataUrl; - } - - // If the `OpenIdMetadataUrl` setting is not specified, use the default based on `IsGov`. This is what is used to authenticate Entra ID tokens. - string openIdMetadataUrl = tokenValidationSection["OpenIdMetadataUrl"]; - if (string.IsNullOrEmpty(openIdMetadataUrl)) - { - openIdMetadataUrl = isGov ? AuthenticationConstants.GovOpenIdMetadataUrl : AuthenticationConstants.PublicOpenIdMetadataUrl; - } - - TimeSpan openIdRefreshInterval = tokenValidationSection.GetValue("OpenIdMetadataRefresh", BaseConfigurationManager.DefaultAutomaticRefreshInterval); - - _ = services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - }) - .AddJwtBearer(options => - { - options.SaveToken = true; - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidateAudience = true, - ValidateLifetime = true, - ClockSkew = TimeSpan.FromMinutes(5), - ValidIssuers = validTokenIssuers, - ValidAudiences = audiences, - ValidateIssuerSigningKey = true, - RequireSignedTokens = true, - }; - - // Using Microsoft.IdentityModel.Validators - options.TokenValidationParameters.EnableAadSigningKeyIssuerValidation(); - - options.Events = new JwtBearerEvents - { - // Create a ConfigurationManager based on the requestor. This is to handle ABS non-Entra tokens. - OnMessageReceived = async context => - { - string authorizationHeader = context.Request.Headers.Authorization.ToString(); - - if (string.IsNullOrEmpty(authorizationHeader)) - { - // Default to AadTokenValidation handling - context.Options.TokenValidationParameters.ConfigurationManager ??= options.ConfigurationManager as BaseConfigurationManager; - await Task.CompletedTask.ConfigureAwait(false); - return; - } - - string[] parts = authorizationHeader?.Split(' '); - if (parts.Length != 2 || parts[0] != "Bearer") - { - // Default to AadTokenValidation handling - context.Options.TokenValidationParameters.ConfigurationManager ??= options.ConfigurationManager as BaseConfigurationManager; - await Task.CompletedTask.ConfigureAwait(false); - return; - } - - JwtSecurityToken token = new(parts[1]); - string issuer = token.Claims.FirstOrDefault(claim => claim.Type == AuthenticationConstants.IssuerClaim)?.Value; - - if (azureBotServiceTokenHandling && AuthenticationConstants.BotFrameworkTokenIssuer.Equals(issuer)) - { - // Use the Azure Bot authority for this configuration manager - context.Options.TokenValidationParameters.ConfigurationManager = _openIdMetadataCache.GetOrAdd(azureBotServiceOpenIdMetadataUrl, key => - { - return new ConfigurationManager(azureBotServiceOpenIdMetadataUrl, new OpenIdConnectConfigurationRetriever(), new HttpClient()) - { - AutomaticRefreshInterval = openIdRefreshInterval - }; - }); - } - else - { - context.Options.TokenValidationParameters.ConfigurationManager = _openIdMetadataCache.GetOrAdd(openIdMetadataUrl, key => - { - return new ConfigurationManager(openIdMetadataUrl, new OpenIdConnectConfigurationRetriever(), new HttpClient()) - { - AutomaticRefreshInterval = openIdRefreshInterval - }; - }); - } - - await Task.CompletedTask.ConfigureAwait(false); - }, - - OnTokenValidated = context => - { - logger?.LogDebug("TOKEN Validated"); - return Task.CompletedTask; - }, - OnForbidden = context => - { - logger?.LogWarning("Forbidden: {m}", context.Result.ToString()); - return Task.CompletedTask; - }, - OnAuthenticationFailed = context => - { - logger?.LogWarning("Auth Failed {m}", context.Exception.ToString()); - return Task.CompletedTask; - } - }; - }); - } -} diff --git a/samples/dotnet/RetrievalBot/Plugins/BuildRetrievalPlugin.cs b/samples/dotnet/RetrievalBot/Plugins/BuildRetrievalPlugin.cs index 1c429aff..ad421e95 100644 --- a/samples/dotnet/RetrievalBot/Plugins/BuildRetrievalPlugin.cs +++ b/samples/dotnet/RetrievalBot/Plugins/BuildRetrievalPlugin.cs @@ -46,7 +46,9 @@ public class BuildRetrievalPlugin(AgentApplication app) [KernelFunction] public async Task BuildRetrievalAsync(string userquery) { - string accessToken = _app.Authorization.GetTurnToken("graph"); +#pragma warning disable CS0618 // Type or member is obsolete + string accessToken = _app.UserAuthorization.GetTurnToken("graph"); +#pragma warning restore CS0618 // Type or member is obsolete var tokenProvider = new StaticTokenProvider(accessToken); var authProvider = new BaseBearerTokenAuthenticationProvider(tokenProvider); var requestAdapter = new HttpClientRequestAdapter(authProvider); diff --git a/samples/dotnet/RetrievalBot/Plugins/DateTimePlugin.cs b/samples/dotnet/RetrievalBot/Plugins/DateTimePlugin.cs index ee0774ef..3d8f57b2 100644 --- a/samples/dotnet/RetrievalBot/Plugins/DateTimePlugin.cs +++ b/samples/dotnet/RetrievalBot/Plugins/DateTimePlugin.cs @@ -21,7 +21,7 @@ public class DateTimePlugin /// /// The current date [KernelFunction, Description("Get the current date")] - public string Date(IFormatProvider formatProvider = null) + public string Date(IFormatProvider? formatProvider = null) { // Example: Sunday, 12 January, 2025 var date = DateTimeOffset.Now.ToString("D", formatProvider); @@ -37,7 +37,7 @@ public string Date(IFormatProvider formatProvider = null) /// /// The current date [KernelFunction, Description("Get the current date")] - public string Today(IFormatProvider formatProvider = null) => + public string Today(IFormatProvider? formatProvider = null) => // Example: Sunday, 12 January, 2025 this.Date(formatProvider); diff --git a/samples/dotnet/RetrievalBot/Program.cs b/samples/dotnet/RetrievalBot/Program.cs index a3fa152d..3a1757ca 100644 --- a/samples/dotnet/RetrievalBot/Program.cs +++ b/samples/dotnet/RetrievalBot/Program.cs @@ -2,10 +2,8 @@ // Licensed under the MIT License. using Microsoft.Agents.Builder; -using Microsoft.Agents.Builder.App; using Microsoft.Agents.Builder.State; using Microsoft.Agents.Hosting.AspNetCore; -using Microsoft.Agents.Samples; using Microsoft.Agents.Storage; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -14,7 +12,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.SemanticKernel; using RetrievalBot; -using RetrievalBot.Agents; using System.Threading; @@ -36,9 +33,9 @@ if (builder.Configuration.GetSection("AIServices").GetValue("UseAzureOpenAI")) { builder.Services.AddAzureOpenAIChatCompletion( - deploymentName: builder.Configuration.GetSection("AIServices:AzureOpenAI").GetValue("DeploymentName"), - endpoint: builder.Configuration.GetSection("AIServices:AzureOpenAI").GetValue("Endpoint"), - apiKey: builder.Configuration.GetSection("AIServices:AzureOpenAI").GetValue("ApiKey")); + deploymentName: builder.Configuration.GetSection("AIServices:AzureOpenAI").GetValue("DeploymentName")!, + endpoint: builder.Configuration.GetSection("AIServices:AzureOpenAI").GetValue("Endpoint")!, + apiKey: builder.Configuration.GetSection("AIServices:AzureOpenAI").GetValue("ApiKey")!); /* //Use the Azure CLI (for local) or Managed Identity (for Azure running app) to authenticate to the Azure OpenAI service credentials: new ChainedTokenCredential( @@ -49,8 +46,8 @@ else { builder.Services.AddOpenAIChatCompletion( - modelId: builder.Configuration.GetSection("AIServices:OpenAI").GetValue("ModelId"), - apiKey: builder.Configuration.GetSection("AIServices:OpenAI").GetValue("ApiKey")); + modelId: builder.Configuration.GetSection("AIServices:OpenAI").GetValue("ModelId")!, + apiKey: builder.Configuration.GetSection("AIServices:OpenAI").GetValue("ApiKey")!); } diff --git a/samples/dotnet/RetrievalBot/README.md b/samples/dotnet/RetrievalBot/README.md index 4ca410e5..3bd13b36 100644 --- a/samples/dotnet/RetrievalBot/README.md +++ b/samples/dotnet/RetrievalBot/README.md @@ -10,7 +10,6 @@ This Agent Sample is intended to introduce you to the Copilot Retrieval API Grou - [.NET](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) version 8.0 - [Dev Tunnels](https://learn.microsoft.com/azure/developer/dev-tunnels/get-started) -- [Bot Framework Emulator](https://github.com/Microsoft/BotFramework-Emulator/releases) for Testing Web Chat. - Download and install Visual Studio (I have 2022 version). - You need Azure subscription to create Azure Bot Service. Follow the steps here – Link TBD - Have Git available on your computer [Git - Installing Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) @@ -27,8 +26,6 @@ This Agent Sample is intended to introduce you to the Copilot Retrieval API Grou - Access to an Azure Subscription with access to preform the following tasks: - Create and configure [Entra ID Application Identities](https://aka.ms/AgentsSDK-CreateBot) - - Create and configure an [Azure Bot Service](https://aka.ms/azuredeployment) for your bot - - Create and configure an [Azure App Service](https://learn.microsoft.com/azure/app-service/) to deploy your bot on to. - A tunneling tool to allow for local development and debugging should you wish to do local development whilst connected to a external client such as Microsoft Teams. 1. Configure your AI service settings. The sample provides configuration placeholders for using Azure OpenAI or OpenAI, but others can be used as well. @@ -70,24 +67,6 @@ This Agent Sample is intended to introduce you to the Copilot Retrieval API Grou 1. `AIServices__OpenAI__ApiKey` - Your OpenAI API key 1. `AIServices__UseAzureOpenAI` - `false` -## Getting Started with RetrievalBot Sample - -Read more about [Running an Agent](../../../docs/HowTo/running-an-agent.md) - -### QuickStart using Bot Framework Emulator - -1. Open the RetrievalBot Sample in Visual Studio 2022 -1. Run it in Debug Mode (F5) -1. A blank web page will open, note down the URL which should be similar too `http://localhost:65349/` -1. Open the [BotFramework Emulator](https://github.com/Microsoft/BotFramework-Emulator/releases) - 1. Click **Open Bot** - 1. In the bot URL field input the URL you noted down from the web page and add /api/messages to it. It should appear similar to `http://localhost:65349/api/messages` - 1. Click **Connect** - -If all is working correctly, the Bot Emulator should show you a Web Chat experience with the words **"Hello and Welcome! I'm here to help with all your weather forecast needs!"** - -If you type a message and hit enter, or the send arrow, you should receive a message asking for more information, or with a weather forecast card. - ### QuickStart using WebChat 1. Create an Azure Bot @@ -102,15 +81,15 @@ If you type a message and hit enter, or the send arrow, you should receive a mes ```json "TokenValidation": { + "Enabled": true, "Audiences": [ - "00000000-0000-0000-0000-000000000000" // this is the Client ID used for the Azure Bot - ] + "{{ClientId}}" // this is the Client ID used for the Azure Bot + ], + "TenantId": "{{TenantId}}" }, "Connections": { - "BotServiceConnection": { - "Assembly": "Microsoft.Agents.Authentication.Msal", - "Type": "MsalAuth", + "ServiceConnection": { "Settings": { "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", @@ -118,8 +97,7 @@ If you type a message and hit enter, or the send arrow, you should receive a mes "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. "Scopes": [ "https://api.botframework.com/.default" - ], - "TenantId": "{{TenantId}}" // This is the Tenant ID used for the Connection. + ] } } ``` @@ -132,7 +110,6 @@ If you type a message and hit enter, or the send arrow, you should receive a mes > Storing sensitive values in appsettings is not recommend. Follow [AspNet Configuration](https://learn.microsoft.com/aspnet/core/fundamentals/configuration/?view=aspnetcore-9.0) for best practices. 1. Run `dev tunnels`. Please follow [Create and host a dev tunnel](https://learn.microsoft.com/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below: - > NOTE: Go to your project directory and open the `./Properties/launchSettings.json` file. Check the port number and use that port number in the devtunnel command (instead of 3978). ```bash devtunnel host -p 3978 --allow-anonymous @@ -153,4 +130,4 @@ If you type a message and hit enter, or the send arrow, you should receive a mes ## Further reading -To learn more about building Bots and Agents, see our [Microsoft 365 Agents SDK](https://github.com/microsoft/agents) repo. +To learn more about building Agents, see [Microsoft 365 Agents SDK](https://learn.microsoft.com/en-us/microsoft-365/agents-sdk/). \ No newline at end of file diff --git a/samples/dotnet/RetrievalBot/RetrievalBot.cs b/samples/dotnet/RetrievalBot/RetrievalBot.cs index 669c9279..484eea53 100644 --- a/samples/dotnet/RetrievalBot/RetrievalBot.cs +++ b/samples/dotnet/RetrievalBot/RetrievalBot.cs @@ -15,7 +15,7 @@ namespace RetrievalBot { public class Retrieval: AgentApplication { - Kernel _kernel = null; + private readonly Kernel _kernel; public Retrieval(AgentApplicationOptions options, Kernel kernel) : base (options) { diff --git a/samples/dotnet/RetrievalBot/RetrievalBot.csproj b/samples/dotnet/RetrievalBot/RetrievalBot.csproj index c5dc92ed..36a56f9e 100644 --- a/samples/dotnet/RetrievalBot/RetrievalBot.csproj +++ b/samples/dotnet/RetrievalBot/RetrievalBot.csproj @@ -8,6 +8,7 @@ b842df34-390f-490d-9dc0-73909363ad16 false false + enable @@ -18,9 +19,7 @@ - - @@ -28,8 +27,8 @@ - - + + diff --git a/samples/dotnet/auto-signin/AspNetExtensions.cs b/samples/dotnet/auto-signin/AspNetExtensions.cs index 11e047ca..5ffbff7e 100644 --- a/samples/dotnet/auto-signin/AspNetExtensions.cs +++ b/samples/dotnet/auto-signin/AspNetExtensions.cs @@ -33,15 +33,34 @@ public static class AspNetExtensions /// /// This extension reads settings from configuration. If configuration is missing JWT token /// is not enabled. - /// The minimum, but typical, configuration is: + ///

The minimum, but typical, configuration is:

/// /// "TokenValidation": { + /// "Enabled": boolean, /// "Audiences": [ /// "{{ClientId}}" // this is the Client ID used for the Azure Bot /// ], /// "TenantId": "{{TenantId}}" /// } /// + /// The full options are: + /// + /// "TokenValidation": { + /// "Enabled": boolean, + /// "Audiences": [ + /// "{required:agent-appid}" + /// ], + /// "TenantId": "{recommended:tenant-id}", + /// "ValidIssuers": [ + /// "{default:Public-AzureBotService}" + /// ], + /// "IsGov": {optional:false}, + /// "AzureBotServiceOpenIdMetadataUrl": optional, + /// "OpenIdMetadataUrl": optional, + /// "AzureBotServiceTokenHandling": "{optional:true}" + /// "OpenIdMetadataRefresh": "optional-12:00:00" + /// } + /// ///
public static void AddAgentAspNetAuthentication(this IServiceCollection services, IConfiguration configuration, string tokenValidationSectionName = "TokenValidation") { diff --git a/samples/dotnet/azure-ai-streaming/AspNetExtensions.cs b/samples/dotnet/azure-ai-streaming/AspNetExtensions.cs index 11e047ca..5ffbff7e 100644 --- a/samples/dotnet/azure-ai-streaming/AspNetExtensions.cs +++ b/samples/dotnet/azure-ai-streaming/AspNetExtensions.cs @@ -33,15 +33,34 @@ public static class AspNetExtensions /// /// This extension reads settings from configuration. If configuration is missing JWT token /// is not enabled. - /// The minimum, but typical, configuration is: + ///

The minimum, but typical, configuration is:

/// /// "TokenValidation": { + /// "Enabled": boolean, /// "Audiences": [ /// "{{ClientId}}" // this is the Client ID used for the Azure Bot /// ], /// "TenantId": "{{TenantId}}" /// } /// + /// The full options are: + /// + /// "TokenValidation": { + /// "Enabled": boolean, + /// "Audiences": [ + /// "{required:agent-appid}" + /// ], + /// "TenantId": "{recommended:tenant-id}", + /// "ValidIssuers": [ + /// "{default:Public-AzureBotService}" + /// ], + /// "IsGov": {optional:false}, + /// "AzureBotServiceOpenIdMetadataUrl": optional, + /// "OpenIdMetadataUrl": optional, + /// "AzureBotServiceTokenHandling": "{optional:true}" + /// "OpenIdMetadataRefresh": "optional-12:00:00" + /// } + /// ///
public static void AddAgentAspNetAuthentication(this IServiceCollection services, IConfiguration configuration, string tokenValidationSectionName = "TokenValidation") { diff --git a/samples/dotnet/copilotstudio-skill/AspNetExtensions.cs b/samples/dotnet/copilotstudio-skill/AspNetExtensions.cs index 11e047ca..5ffbff7e 100644 --- a/samples/dotnet/copilotstudio-skill/AspNetExtensions.cs +++ b/samples/dotnet/copilotstudio-skill/AspNetExtensions.cs @@ -33,15 +33,34 @@ public static class AspNetExtensions /// /// This extension reads settings from configuration. If configuration is missing JWT token /// is not enabled. - /// The minimum, but typical, configuration is: + ///

The minimum, but typical, configuration is:

/// /// "TokenValidation": { + /// "Enabled": boolean, /// "Audiences": [ /// "{{ClientId}}" // this is the Client ID used for the Azure Bot /// ], /// "TenantId": "{{TenantId}}" /// } /// + /// The full options are: + /// + /// "TokenValidation": { + /// "Enabled": boolean, + /// "Audiences": [ + /// "{required:agent-appid}" + /// ], + /// "TenantId": "{recommended:tenant-id}", + /// "ValidIssuers": [ + /// "{default:Public-AzureBotService}" + /// ], + /// "IsGov": {optional:false}, + /// "AzureBotServiceOpenIdMetadataUrl": optional, + /// "OpenIdMetadataUrl": optional, + /// "AzureBotServiceTokenHandling": "{optional:true}" + /// "OpenIdMetadataRefresh": "optional-12:00:00" + /// } + /// ///
public static void AddAgentAspNetAuthentication(this IServiceCollection services, IConfiguration configuration, string tokenValidationSectionName = "TokenValidation") { diff --git a/samples/dotnet/obo-authorization/AspNetExtensions.cs b/samples/dotnet/obo-authorization/AspNetExtensions.cs index 11e047ca..5ffbff7e 100644 --- a/samples/dotnet/obo-authorization/AspNetExtensions.cs +++ b/samples/dotnet/obo-authorization/AspNetExtensions.cs @@ -33,15 +33,34 @@ public static class AspNetExtensions /// /// This extension reads settings from configuration. If configuration is missing JWT token /// is not enabled. - /// The minimum, but typical, configuration is: + ///

The minimum, but typical, configuration is:

/// /// "TokenValidation": { + /// "Enabled": boolean, /// "Audiences": [ /// "{{ClientId}}" // this is the Client ID used for the Azure Bot /// ], /// "TenantId": "{{TenantId}}" /// } /// + /// The full options are: + /// + /// "TokenValidation": { + /// "Enabled": boolean, + /// "Audiences": [ + /// "{required:agent-appid}" + /// ], + /// "TenantId": "{recommended:tenant-id}", + /// "ValidIssuers": [ + /// "{default:Public-AzureBotService}" + /// ], + /// "IsGov": {optional:false}, + /// "AzureBotServiceOpenIdMetadataUrl": optional, + /// "OpenIdMetadataUrl": optional, + /// "AzureBotServiceTokenHandling": "{optional:true}" + /// "OpenIdMetadataRefresh": "optional-12:00:00" + /// } + /// ///
public static void AddAgentAspNetAuthentication(this IServiceCollection services, IConfiguration configuration, string tokenValidationSectionName = "TokenValidation") { diff --git a/samples/dotnet/quickstart/AspNetExtensions.cs b/samples/dotnet/quickstart/AspNetExtensions.cs index 3177d0a6..5ffbff7e 100644 --- a/samples/dotnet/quickstart/AspNetExtensions.cs +++ b/samples/dotnet/quickstart/AspNetExtensions.cs @@ -19,8 +19,6 @@ using System.Net.Http; using System.Threading.Tasks; -namespace Microsoft.Agents.AspNetAuthentication; - public static class AspNetExtensions { private static readonly ConcurrentDictionary> _openIdMetadataCache = new(); @@ -35,15 +33,34 @@ public static class AspNetExtensions /// /// This extension reads settings from configuration. If configuration is missing JWT token /// is not enabled. - /// The minimum, but typical, configuration is: + ///

The minimum, but typical, configuration is:

/// /// "TokenValidation": { + /// "Enabled": boolean, /// "Audiences": [ /// "{{ClientId}}" // this is the Client ID used for the Azure Bot /// ], /// "TenantId": "{{TenantId}}" /// } /// + /// The full options are: + /// + /// "TokenValidation": { + /// "Enabled": boolean, + /// "Audiences": [ + /// "{required:agent-appid}" + /// ], + /// "TenantId": "{recommended:tenant-id}", + /// "ValidIssuers": [ + /// "{default:Public-AzureBotService}" + /// ], + /// "IsGov": {optional:false}, + /// "AzureBotServiceOpenIdMetadataUrl": optional, + /// "OpenIdMetadataUrl": optional, + /// "AzureBotServiceTokenHandling": "{optional:true}" + /// "OpenIdMetadataRefresh": "optional-12:00:00" + /// } + /// ///
public static void AddAgentAspNetAuthentication(this IServiceCollection services, IConfiguration configuration, string tokenValidationSectionName = "TokenValidation") { diff --git a/samples/dotnet/quickstart/Program.cs b/samples/dotnet/quickstart/Program.cs index 6c6fac55..a0c96e46 100644 --- a/samples/dotnet/quickstart/Program.cs +++ b/samples/dotnet/quickstart/Program.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using QuickStart; -using Microsoft.Agents.AspNetAuthentication; using Microsoft.Agents.Builder; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Storage; diff --git a/samples/dotnet/semantic-kernel-multiturn/AspNetExtensions.cs b/samples/dotnet/semantic-kernel-multiturn/AspNetExtensions.cs index 11e047ca..5ffbff7e 100644 --- a/samples/dotnet/semantic-kernel-multiturn/AspNetExtensions.cs +++ b/samples/dotnet/semantic-kernel-multiturn/AspNetExtensions.cs @@ -33,15 +33,34 @@ public static class AspNetExtensions /// /// This extension reads settings from configuration. If configuration is missing JWT token /// is not enabled. - /// The minimum, but typical, configuration is: + ///

The minimum, but typical, configuration is:

/// /// "TokenValidation": { + /// "Enabled": boolean, /// "Audiences": [ /// "{{ClientId}}" // this is the Client ID used for the Azure Bot /// ], /// "TenantId": "{{TenantId}}" /// } /// + /// The full options are: + /// + /// "TokenValidation": { + /// "Enabled": boolean, + /// "Audiences": [ + /// "{required:agent-appid}" + /// ], + /// "TenantId": "{recommended:tenant-id}", + /// "ValidIssuers": [ + /// "{default:Public-AzureBotService}" + /// ], + /// "IsGov": {optional:false}, + /// "AzureBotServiceOpenIdMetadataUrl": optional, + /// "OpenIdMetadataUrl": optional, + /// "AzureBotServiceTokenHandling": "{optional:true}" + /// "OpenIdMetadataRefresh": "optional-12:00:00" + /// } + /// ///
public static void AddAgentAspNetAuthentication(this IServiceCollection services, IConfiguration configuration, string tokenValidationSectionName = "TokenValidation") {