diff --git a/eng/Dependencies.props b/eng/Dependencies.props index d86ab879db..c242829120 100644 --- a/eng/Dependencies.props +++ b/eng/Dependencies.props @@ -168,7 +168,6 @@ and are generated based on the last package release. - diff --git a/eng/Versions.props b/eng/Versions.props index ea2917827a..4cd8dc2f0d 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -260,9 +260,8 @@ 4.0.4 4.0.4 2.1.90 - 0.2.3-preview - 0.2.3-preview - 3.8.0 + 0.3.1-preview + 0.3.1-preview $(MessagePackPackageVersion) 4.10.0 0.11.2 diff --git a/src/ProjectTemplates/Web.ProjectTemplates/BlazorServerWeb-CSharp.csproj.in b/src/ProjectTemplates/Web.ProjectTemplates/BlazorServerWeb-CSharp.csproj.in index c47e9753cd..bfebf3e0f2 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/BlazorServerWeb-CSharp.csproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/BlazorServerWeb-CSharp.csproj.in @@ -25,7 +25,6 @@ - diff --git a/src/ProjectTemplates/Web.ProjectTemplates/ComponentsWebAssembly-CSharp.Server.csproj.in b/src/ProjectTemplates/Web.ProjectTemplates/ComponentsWebAssembly-CSharp.Server.csproj.in index 20efa3e9e2..c424c7f614 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/ComponentsWebAssembly-CSharp.Server.csproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/ComponentsWebAssembly-CSharp.Server.csproj.in @@ -40,7 +40,6 @@ - diff --git a/src/ProjectTemplates/Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in b/src/ProjectTemplates/Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in index 13f3a99275..d7d8786b54 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in @@ -25,7 +25,6 @@ - diff --git a/src/ProjectTemplates/Web.ProjectTemplates/StarterWeb-CSharp.csproj.in b/src/ProjectTemplates/Web.ProjectTemplates/StarterWeb-CSharp.csproj.in index 205c905eb6..2f77d16ec4 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/StarterWeb-CSharp.csproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/StarterWeb-CSharp.csproj.in @@ -25,7 +25,6 @@ - diff --git a/src/ProjectTemplates/Web.ProjectTemplates/WebApi-CSharp.csproj.in b/src/ProjectTemplates/Web.ProjectTemplates/WebApi-CSharp.csproj.in index fd6d56ec32..064078a06d 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/WebApi-CSharp.csproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/WebApi-CSharp.csproj.in @@ -10,7 +10,6 @@ - diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json index 463e0eb7ba..020748fbc4 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json @@ -138,8 +138,6 @@ { "condition": "(!GenerateGraph)", "exclude": [ - "Services/MicrosoftGraphServiceExtensions.cs", - "Services/TokenAcquisitionCredentialProvider.cs", "Shared/NavMenu.CallsMicrosoftGraph.razor", "Pages/ShowProfile.razor" ] diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/CallWebApi.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/CallWebApi.razor index 858bf6f09f..d3bad28a25 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/CallWebApi.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/CallWebApi.razor @@ -21,13 +21,25 @@ else } @code { + private HttpResponseMessage response; private string apiResult; protected override async Task OnInitializedAsync() { try { - apiResult = await downstreamAPI.CallWebApiAsync("me"); + response = await downstreamAPI.CallWebApiForUserAsync( + "DownstreamApi", + options => options.RelativePath = ""); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) + { + apiResult = await response.Content.ReadAsStringAsync(); + } + else + { + apiResult = "Failed to call the web API"; + } } catch (Exception ex) { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Services/DownstreamWebApi.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Services/DownstreamWebApi.cs deleted file mode 100644 index fbb0de85f6..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Services/DownstreamWebApi.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Identity.Web; - -namespace BlazorServerWeb_CSharp -{ - public interface IDownstreamWebApi - { - Task CallWebApiAsync(string relativeEndpoint = "", string[] requiredScopes = null); - } - - public static class DownstreamWebApiExtensions - { - public static void AddDownstreamWebApiService(this IServiceCollection services, IConfiguration configuration) - { - // https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests - services.AddHttpClient(); - } - } - - public class DownstreamWebApi : IDownstreamWebApi - { - private readonly ITokenAcquisition _tokenAcquisition; - - private readonly IConfiguration _configuration; - - private readonly HttpClient _httpClient; - - public DownstreamWebApi( - ITokenAcquisition tokenAcquisition, - IConfiguration configuration, - HttpClient httpClient) - { - _tokenAcquisition = tokenAcquisition; - _configuration = configuration; - _httpClient = httpClient; - } - - /// - /// Calls the Web API with the required scopes - /// - /// [Optional] Scopes required to call the Web API. If - /// not specified, uses scopes from the configuration - /// Endpoint relative to the CalledApiUrl configuration - /// A JSON string representing the result of calling the Web API - public async Task CallWebApiAsync(string relativeEndpoint = "", string[] requiredScopes = null) - { - string[] scopes = requiredScopes ?? _configuration["CalledApi:CalledApiScopes"]?.Split(' '); - string apiUrl = (_configuration["CalledApi:CalledApiUrl"] as string)?.TrimEnd('/') + $"/{relativeEndpoint}"; - - string accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes); - HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, apiUrl); - httpRequestMessage.Headers.Add("Authorization", $"bearer {accessToken}"); - - string apiResult; - var response = await _httpClient.SendAsync(httpRequestMessage); - if (response.StatusCode == HttpStatusCode.OK) - { - apiResult = await response.Content.ReadAsStringAsync(); - } - else - { - apiResult = $"Error calling the API '{apiUrl}'"; - } - - return apiResult; - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Services/MicrosoftGraphServiceExtensions.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Services/MicrosoftGraphServiceExtensions.cs deleted file mode 100644 index f917fbed58..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Services/MicrosoftGraphServiceExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Graph; -using Microsoft.Identity.Web; - -namespace BlazorServerWeb_CSharp -{ - public static class MicrosoftGraphServiceExtensions - { - /// - /// Adds the Microsoft Graph client as a singleton. - /// - /// Service collection. - /// Initial scopes. - /// Base URL for Microsoft graph. This can be - /// changed for instance for applications running in national clouds - public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services, - IEnumerable initialScopes, - string graphBaseUrl = "https://graph.microsoft.com/v1.0") - { - services.AddTokenAcquisition(true); - services.AddSingleton(serviceProvider => - { - var tokenAquisitionService = serviceProvider.GetService(); - GraphServiceClient client = string.IsNullOrWhiteSpace(graphBaseUrl) ? - new GraphServiceClient(new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)) : - new GraphServiceClient(graphBaseUrl, new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)); - return client; - }); - return services; - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Services/TokenAcquisitionCredentialProvider.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Services/TokenAcquisitionCredentialProvider.cs deleted file mode 100644 index 5d6c643ca4..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Services/TokenAcquisitionCredentialProvider.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.Graph; -using Microsoft.Identity.Web; -using System.Collections; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; - -namespace BlazorServerWeb_CSharp -{ - internal class TokenAcquisitionCredentialProvider : IAuthenticationProvider - { - public TokenAcquisitionCredentialProvider(ITokenAcquisition tokenAcquisition, IEnumerable initialScopes) - { - _tokenAcquisition = tokenAcquisition; - _initialScopes = initialScopes; - } - - ITokenAcquisition _tokenAcquisition; - IEnumerable _initialScopes; - - public async Task AuthenticateRequestAsync(HttpRequestMessage request) - { - request.Headers.Add("Authorization", - $"Bearer {await _tokenAcquisition.GetAccessTokenForUserAsync(_initialScopes)}"); - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Startup.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Startup.cs index 72c0379b57..652c82a570 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Startup.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Startup.cs @@ -4,9 +4,9 @@ using System.Linq; using System.Threading.Tasks; #if (OrganizationalAuth || IndividualB2CAuth) using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.Identity.Web; using Microsoft.Identity.Web.UI; -using Microsoft.Identity.Web.TokenCacheProviders.InMemory; #endif #if (OrganizationalAuth) #if (MultiOrgAuth) @@ -73,33 +73,36 @@ namespace BlazorServerWeb_CSharp .AddEntityFrameworkStores(); #elif (OrganizationalAuth) #if (GenerateApiOrGraph) - string[] scopes = Configuration.GetValue("CalledApi:CalledApiScopes")?.Split(' '); + var initialScopes = Configuration.GetValue("DownstreamApi:Scopes")?.Split(' '); + #endif + services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) #if (GenerateApiOrGraph) - services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd") - .AddMicrosoftWebAppCallsWebApi(Configuration, scopes, "AzureAd") - .AddInMemoryTokenCaches(); -#else - services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd"); -#endif + .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) #if (GenerateApi) - services.AddDownstreamWebApiService(Configuration); + .AddDownstreamWebApi("DownstreamApi", Configuration.GetSection("DownstreamApi")) #endif #if (GenerateGraph) - services.AddMicrosoftGraph(scopes, Configuration.GetValue("CalledApi:CalledApiUrl")); + .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi")) +#endif + .AddInMemoryTokenCaches(); +#else + .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd")); #endif #elif (IndividualB2CAuth) #if (GenerateApi) - string[] scopes = Configuration.GetValue("CalledApi:CalledApiScopes")?.Split(' '); -#endif -#if (GenerateApi) - services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAdB2C") - .AddMicrosoftWebAppCallsWebApi(Configuration, scopes, "AzureAdB2C") - .AddInMemoryTokenCaches(); + var initialScopes = Configuration.GetValue("DownstreamApi:Scopes")?.Split(' '); - services.AddDownstreamWebApiService(Configuration); +#endif + services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) +#if (GenerateApi) + .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) + .AddDownstreamWebApi("DownstreamApi", Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); #else - services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAdB2C"); + .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C")); #endif #endif #if (OrganizationalAuth || IndividualB2CAuth) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/appsettings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/appsettings.json index b2b1a5fa42..1c0516223b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/appsettings.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/appsettings.json @@ -17,10 +17,10 @@ // }, ////#elseif (OrganizationalAuth) // "AzureAd": { -//#if (MultiOrgAuth) -// "Instance": "https:////login.microsoftonline.com/common", -//#elseif (SingleOrgAuth) // "Instance": "https:////login.microsoftonline.com/", +//#if (MultiOrgAuth) +// "TenantId": "common", +//#elseif (SingleOrgAuth) // "Domain": "qualified.domain.name", // "TenantId": "22222222-2222-2222-2222-222222222222", //#endif @@ -34,16 +34,16 @@ // }, ////#endif ////#if (GenerateApiOrGraph) -// "CalledApi": { +// "DownstreamApi": { // /* -// 'CalledApiScopes' contains space separated scopes of the Web API you want to call. This can be: +// 'Scopes' contains space separated scopes of the Web API you want to call. This can be: // - a scope for a V2 application (for instance api://b3682cc7-8b30-4bd2-aaba-080c6bf0fd31/access_as_user) // - a scope corresponding to a V1 application (for instance /.default, where is the // App ID URI of a legacy v1 Web application // Applications are registered in the https://portal.azure.com portal. // */ -// "CalledApiScopes": "user.read", -// "CalledApiUrl": "[WebApiUrl]" +// "BaseUrl": "[WebApiUrl]", +// "Scopes": "user.read" // }, ////#endif ////#if (IndividualLocalAuth) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json index 74be1e1bff..0e8fd2766f 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json @@ -211,19 +211,6 @@ "Server/Controllers/OidcConfigurationController.cs", "Server/Models/ApplicationUser.cs" ] - }, - { - "condition": "(Hosted && !GenerateApi)", - "exclude": [ - "Server/Services/DownstreamWebApi.cs" - ] - }, - { - "condition": "(Hosted &&!GenerateGraph)", - "exclude": [ - "Server/Services/MicrosoftGraphServiceExtensions.cs", - "Server/Services/TokenAcquisitionCredentialProvider.cs" - ] } ] } @@ -301,7 +288,7 @@ "SignUpSignInPolicyId": { "type": "parameter", "datatype": "string", - "defaultValue": "", + "defaultValue": "b2c_1_susi", "replaces": "MySignUpSignInPolicyId", "description": "The sign-in and sign-up policy ID for this project (use with IndividualB2C auth)." }, @@ -340,7 +327,7 @@ "type": "parameter", "datatype": "string", "replaces": "api-scope", - "defaultValue": "user_impersonation", + "defaultValue": "access_as_user", "description": "The API scope the client needs to request to provision an access token. (use with IndividualB2C, SingleOrg)." }, "TenantId": { @@ -470,7 +457,7 @@ "type": "parameter", "datatype": "string", "replaces": "[WebApiUrl]", - "defaultValue" : "https://graph.microsoft.com/v1.0/me", + "defaultValue": "https://graph.microsoft.com/v1.0", "description": "URL of the API to call from the web app. This option only applies if --auth SingleOrg, --auth MultiOrg or --auth IndividualB2C without and ASP.NET Core host is specified." }, "CallsMicrosoftGraph": { @@ -487,7 +474,7 @@ }, "GenerateApi": { "type": "computed", - "value": "(( (IndividualB2CAuth && !Hosted) || OrganizationalAuth) && (CalledApiUrl != \"https://graph.microsoft.com/v1.0/me\" || CalledApiScopes != \"user.read\"))" + "value": "(( (IndividualB2CAuth && !Hosted) || OrganizationalAuth) && (CalledApiUrl != \"https://graph.microsoft.com/v1.0\" || CalledApiScopes != \"user.read\"))" }, "GenerateGraph": { "type": "computed", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Controllers/WeatherForecastController.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Controllers/WeatherForecastController.cs index 99971dfae8..b2e4339447 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Controllers/WeatherForecastController.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Controllers/WeatherForecastController.cs @@ -15,10 +15,10 @@ using System.Net.Http; using Microsoft.Graph; #endif using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; #if (OrganizationalAuth || IndividualB2CAuth) using Microsoft.Identity.Web.Resource; #endif -using Microsoft.Extensions.Logging; using ComponentsWebAssembly_CSharp.Shared; namespace ComponentsWebAssembly_CSharp.Server.Controllers @@ -37,7 +37,7 @@ namespace ComponentsWebAssembly_CSharp.Server.Controllers private readonly ILogger _logger; - // The Web API will only accept tokens 1) for users, and 2) having the api-scope scope for this API + // The Web API will only accept tokens 1) for users, and 2) having the "api-scope" scope for this API static readonly string[] scopeRequiredByApi = new string[] { "api-scope" }; #if (GenerateApi) @@ -55,7 +55,17 @@ namespace ComponentsWebAssembly_CSharp.Server.Controllers { HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi); - string downstreamApiResult = await _downstreamWebApi.CallWebApiAsync(); + using var response = await _downstreamWebApi.CallWebApiForUserAsync("DownstreamApi").ConfigureAwait(false); + if (response.StatusCode == System.Net.HttpStatusCode.OK) + { + var apiResult = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + // Do something + } + else + { + var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new HttpRequestException($"Invalid status code in the HttpResponseMessage: {response.StatusCode}: {error}"); + } var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast @@ -75,7 +85,7 @@ namespace ComponentsWebAssembly_CSharp.Server.Controllers { _logger = logger; _graphServiceClient = graphServiceClient; - } + } [HttpGet] public async Task> Get() diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Services/DownstreamWebApi.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Services/DownstreamWebApi.cs deleted file mode 100644 index 0c7d0fcb96..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Services/DownstreamWebApi.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Identity.Web; - -namespace ComponentsWebAssembly_CSharp.Server -{ - public interface IDownstreamWebApi - { - Task CallWebApiAsync(string relativeEndpoint = "", string[] requiredScopes = null); - } - - public static class DownstreamWebApiExtensions - { - public static void AddDownstreamWebApiService(this IServiceCollection services, IConfiguration configuration) - { - // https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests - services.AddHttpClient(); - } - } - - public class DownstreamWebApi : IDownstreamWebApi - { - private readonly ITokenAcquisition _tokenAcquisition; - - private readonly IConfiguration _configuration; - - private readonly HttpClient _httpClient; - - public DownstreamWebApi( - ITokenAcquisition tokenAcquisition, - IConfiguration configuration, - HttpClient httpClient) - { - _tokenAcquisition = tokenAcquisition; - _configuration = configuration; - _httpClient = httpClient; - } - - /// - /// Calls the Web API with the required scopes - /// - /// [Optional] Scopes required to call the Web API. If - /// not specified, uses scopes from the configuration - /// Endpoint relative to the CalledApiUrl configuration - /// A JSON string representing the result of calling the Web API - public async Task CallWebApiAsync(string relativeEndpoint = "", string[] requiredScopes = null) - { - string[] scopes = requiredScopes ?? _configuration["CalledApi:CalledApiScopes"]?.Split(' '); - string apiUrl = (_configuration["CalledApi:CalledApiUrl"] as string)?.TrimEnd('/') + $"/{relativeEndpoint}"; - - string accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes); - HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, apiUrl); - httpRequestMessage.Headers.Add("Authorization", $"bearer {accessToken}"); - - string apiResult; - var response = await _httpClient.SendAsync(httpRequestMessage); - if (response.StatusCode == HttpStatusCode.OK) - { - apiResult = await response.Content.ReadAsStringAsync(); - } - else - { - apiResult = $"Error calling the API '{apiUrl}'"; - } - - return apiResult; - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Services/MicrosoftGraphServiceExtensions.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Services/MicrosoftGraphServiceExtensions.cs deleted file mode 100644 index 6702cc3371..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Services/MicrosoftGraphServiceExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Graph; -using Microsoft.Identity.Web; - -namespace ComponentsWebAssembly_CSharp.Server -{ - public static class MicrosoftGraphServiceExtensions - { - /// - /// Adds the Microsoft Graph client as a singleton. - /// - /// Service collection. - /// Initial scopes. - /// Base URL for Microsoft graph. This can be - /// changed for instance for applications running in national clouds - public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services, - IEnumerable initialScopes, - string graphBaseUrl = "https://graph.microsoft.com/v1.0") - { - services.AddTokenAcquisition(true); - services.AddSingleton(serviceProvider => - { - var tokenAquisitionService = serviceProvider.GetService(); - GraphServiceClient client = string.IsNullOrWhiteSpace(graphBaseUrl) ? - new GraphServiceClient(new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)) : - new GraphServiceClient(graphBaseUrl, new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)); - return client; - }); - return services; - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Services/TokenAcquisitionCredentialProvider.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Services/TokenAcquisitionCredentialProvider.cs deleted file mode 100644 index a6cc2b080e..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Services/TokenAcquisitionCredentialProvider.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.Graph; -using Microsoft.Identity.Web; -using System.Collections; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; - -namespace ComponentsWebAssembly_CSharp.Server -{ - internal class TokenAcquisitionCredentialProvider : IAuthenticationProvider - { - public TokenAcquisitionCredentialProvider(ITokenAcquisition tokenAcquisition, IEnumerable initialScopes) - { - _tokenAcquisition = tokenAcquisition; - _initialScopes = initialScopes; - } - - ITokenAcquisition _tokenAcquisition; - IEnumerable _initialScopes; - - public async Task AuthenticateRequestAsync(HttpRequestMessage request) - { - request.Headers.Add("Authorization", - $"Bearer {await _tokenAcquisition.GetAccessTokenForUserAsync(_initialScopes)}"); - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Startup.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Startup.cs index 80c2560a8e..3ebd062721 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Startup.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Startup.cs @@ -3,8 +3,8 @@ using Microsoft.AspNetCore.Authentication; #endif using Microsoft.AspNetCore.Builder; #if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Identity.Web; -using Microsoft.Identity.Web.TokenCacheProviders.InMemory; #endif #if (RequiresHttps) using Microsoft.AspNetCore.HttpsPolicy; @@ -63,31 +63,29 @@ namespace ComponentsWebAssembly_CSharp.Server .AddIdentityServerJwt(); #endif #if (OrganizationalAuth) + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) #if (GenerateApiOrGraph) - // Adds Microsoft Identity platform (AAD v2.0) support to protect this Api - services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAd") - .AddMicrosoftWebApiCallsWebApi(Configuration, "AzureAd") - .AddInMemoryTokenCaches(); -#else - // Adds Microsoft Identity platform (AAD v2.0) support to protect this Api - services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAd"); -#endif + .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi() #if (GenerateApi) - services.AddDownstreamWebApiService(Configuration); + .AddDownstreamWebApi("DownstreamApi", Configuration.GetSection("DownstreamApi")) #endif #if (GenerateGraph) - services.AddMicrosoftGraph(Configuration.GetValue("CalledApi:CalledApiScopes")?.Split(' '), - Configuration.GetValue("CalledApi:CalledApiUrl")); + .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi")) +#endif + .AddInMemoryTokenCaches(); +#else + .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd")); #endif #elif (IndividualB2CAuth) + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) #if (GenerateApi) - services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAdB2C") - .AddMicrosoftWebApiCallsWebApi(Configuration, "AzureAdB2C") - .AddInMemoryTokenCaches(); - - services.AddDownstreamWebApiService(Configuration); + .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi() + .AddDownstreamWebApi("DownstreamApi", Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); #else - services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAdB2C"); + .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAdB2C")); #endif #endif diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/appsettings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/appsettings.json index da1c94c1b9..b5f991734b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/appsettings.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/appsettings.json @@ -21,10 +21,10 @@ // }, ////#elseif (OrganizationalAuth) // "AzureAd": { -//#if (!SingleOrgAuth) -// "Instance": "https:////login.microsoftonline.com/common", -//#else // "Instance": "https:////login.microsoftonline.com/", +//#if (!SingleOrgAuth) +// "TenantId": "common", +//#else // "Domain": "qualified.domain.name", // "TenantId": "22222222-2222-2222-2222-222222222222", //#endif @@ -38,16 +38,16 @@ // }, ////#endif ////#if (GenerateApiOrGraph) -// "CalledApi": { +// "DownstreamAPI": { // /* -// 'CalledApiScopes' contains space separated scopes of the Web API you want to call. This can be: +// 'Scopes' contains space separated scopes of the Web API you want to call. This can be: // - a scope for a V2 application (for instance api://b3682cc7-8b30-4bd2-aaba-080c6bf0fd31/access_as_user) // - a scope corresponding to a V1 application (for instance /.default, where is the // App ID URI of a legacy v1 Web application // Applications are registered in the https://portal.azure.com portal. // */ -// "CalledApiScopes": "user.read", -// "CalledApiUrl": "[WebApiUrl]" +// "BaseUrl": "[WebApiUrl]", +// "Scopes": "user.read" // }, ////#endif "Logging": { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json index f939274638..a13d60b66a 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json @@ -94,19 +94,6 @@ "exclude": [ "Data/SqlServer/**" ] - }, - { - "condition": "(!GenerateApi)", - "exclude": [ - "Services/DownstreamWebApi.cs" - ] - }, - { - "condition": "(!GenerateGraph)", - "exclude": [ - "Services/MicrosoftGraphServiceExtensions.cs", - "Services/TokenAcquisitionCredentialProvider.cs" - ] } ] } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml.cs index bac6493345..35b806d386 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml.cs @@ -18,7 +18,7 @@ using Microsoft.Extensions.Logging; namespace Company.WebApplication1.Pages { #if (GenerateApiOrGraph) - [AuthorizeForScopes(ScopeKeySection = "CalledApi:CalledApiScopes")] + [AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")] #endif public class IndexModel : PageModel { @@ -36,7 +36,17 @@ namespace Company.WebApplication1.Pages public async Task OnGet() { - ViewData["ApiResult"] = await _downstreamWebApi.CallWebApiAsync(); + using var response = await _downstreamWebApi.CallWebApiForUserAsync("DownstreamApi").ConfigureAwait(false); + if (response.StatusCode == System.Net.HttpStatusCode.OK) + { + var apiResult = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + ViewData["ApiResult"] = apiResult; + } + else + { + var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new HttpRequestException($"Invalid status code in the HttpResponseMessage: {response.StatusCode}: {error}"); + } } #elseif (GenerateGraph) private readonly GraphServiceClient _graphServiceClient; diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Services/DownstreamWebApi.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Services/DownstreamWebApi.cs deleted file mode 100644 index 4806ca192d..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Services/DownstreamWebApi.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Identity.Web; - -namespace Company.WebApplication1 -{ - public interface IDownstreamWebApi - { - Task CallWebApiAsync(string relativeEndpoint = "", string[] requiredScopes = null); - } - - public static class DownstreamWebApiExtensions - { - public static void AddDownstreamWebApiService(this IServiceCollection services, IConfiguration configuration) - { - // https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests - services.AddHttpClient(); - } - } - - public class DownstreamWebApi : IDownstreamWebApi - { - private readonly ITokenAcquisition _tokenAcquisition; - - private readonly IConfiguration _configuration; - - private readonly HttpClient _httpClient; - - public DownstreamWebApi( - ITokenAcquisition tokenAcquisition, - IConfiguration configuration, - HttpClient httpClient) - { - _tokenAcquisition = tokenAcquisition; - _configuration = configuration; - _httpClient = httpClient; - } - - /// - /// Calls the Web API with the required scopes - /// - /// [Optional] Scopes required to call the Web API. If - /// not specified, uses scopes from the configuration - /// Endpoint relative to the CalledApiUrl configuration - /// A JSON string representing the result of calling the Web API - public async Task CallWebApiAsync(string relativeEndpoint = "", string[] requiredScopes = null) - { - string[] scopes = requiredScopes ?? _configuration["CalledApi:CalledApiScopes"]?.Split(' '); - string apiUrl = (_configuration["CalledApi:CalledApiUrl"] as string)?.TrimEnd('/') + $"/{relativeEndpoint}"; - - string accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes); - HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, apiUrl); - httpRequestMessage.Headers.Add("Authorization", $"bearer {accessToken}"); - - string apiResult; - var response = await _httpClient.SendAsync(httpRequestMessage); - if (response.StatusCode == HttpStatusCode.OK) - { - apiResult = await response.Content.ReadAsStringAsync(); - } - else - { - apiResult = $"Error calling the API '{apiUrl}'"; - } - - return apiResult; - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Services/MicrosoftGraphServiceExtensions.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Services/MicrosoftGraphServiceExtensions.cs deleted file mode 100644 index 2e805a9688..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Services/MicrosoftGraphServiceExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Graph; -using Microsoft.Identity.Web; - -namespace Company.WebApplication1 -{ - public static class MicrosoftGraphServiceExtensions - { - /// - /// Adds the Microsoft Graph client as a singleton. - /// - /// Service collection. - /// Initial scopes. - /// Base URL for Microsoft graph. This can be - /// changed for instance for applications running in national clouds - public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services, - IEnumerable initialScopes, - string graphBaseUrl = "https://graph.microsoft.com/v1.0") - { - services.AddTokenAcquisition(true); - services.AddSingleton(serviceProvider => - { - var tokenAquisitionService = serviceProvider.GetService(); - GraphServiceClient client = string.IsNullOrWhiteSpace(graphBaseUrl) ? - new GraphServiceClient(new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)) : - new GraphServiceClient(graphBaseUrl, new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)); - return client; - }); - - return services; - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Services/TokenAcquisitionCredentialProvider.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Services/TokenAcquisitionCredentialProvider.cs deleted file mode 100644 index 8042149d82..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Services/TokenAcquisitionCredentialProvider.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.Graph; -using Microsoft.Identity.Web; -using System.Collections; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Company.WebApplication1 -{ - internal class TokenAcquisitionCredentialProvider : IAuthenticationProvider - { - public TokenAcquisitionCredentialProvider(ITokenAcquisition tokenAcquisition, IEnumerable initialScopes) - { - _tokenAcquisition = tokenAcquisition; - _initialScopes = initialScopes; - } - - ITokenAcquisition _tokenAcquisition; - IEnumerable _initialScopes; - - public async Task AuthenticateRequestAsync(HttpRequestMessage request) - { - request.Headers.Add("Authorization", - $"Bearer {await _tokenAcquisition.GetAccessTokenForUserAsync(_initialScopes)}"); - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Startup.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Startup.cs index c181d5a49c..0114d62081 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Startup.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Startup.cs @@ -4,21 +4,11 @@ using System.Linq; using System.Threading.Tasks; #if (OrganizationalAuth || IndividualB2CAuth) using Microsoft.AspNetCore.Authentication; -#endif -#if (OrganizationalAuth) using Microsoft.Identity.Web; using Microsoft.Identity.Web.UI; -using Microsoft.Identity.Web.TokenCacheProviders.InMemory; -#if (MultiOrgAuth) using Microsoft.AspNetCore.Authentication.OpenIdConnect; -#endif using Microsoft.AspNetCore.Authorization; #endif -#if (IndividualB2CAuth) -using Microsoft.Identity.Web; -using Microsoft.Identity.Web.UI; -using Microsoft.Identity.Web.TokenCacheProviders.InMemory; -#endif using Microsoft.AspNetCore.Builder; #if (IndividualLocalAuth) using Microsoft.AspNetCore.Identity; @@ -73,28 +63,36 @@ namespace Company.WebApplication1 .AddEntityFrameworkStores(); #elif (OrganizationalAuth) #if (GenerateApiOrGraph) - services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd") - .AddMicrosoftWebAppCallsWebApi(Configuration, "AzureAd") - .AddInMemoryTokenCaches(); -#else - services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd"); + var initialScopes = Configuration.GetValue("DownstreamApi:Scopes")?.Split(' '); + #endif + services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) +#if (GenerateApiOrGraph) + .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) #if (GenerateApi) - services.AddDownstreamWebApiService(Configuration); + .AddDownstreamWebApi("DownstreamApi", Configuration.GetSection("DownstreamApi")) #endif #if (GenerateGraph) - services.AddMicrosoftGraph(Configuration.GetValue("CalledApi:CalledApiScopes")?.Split(' '), - Configuration.GetValue("CalledApi:CalledApiUrl")); + .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi")) +#endif + .AddInMemoryTokenCaches(); +#else + .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd")); #endif #elif (IndividualB2CAuth) #if (GenerateApi) - services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAdB2C") - .AddMicrosoftWebAppCallsWebApi(Configuration, "AzureAdB2C") - .AddInMemoryTokenCaches(); + var initialScopes = Configuration.GetValue("DownstreamApi:Scopes")?.Split(' '); - services.AddDownstreamWebApiService(Configuration); +#endif + services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) +#if (GenerateApi) + .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) + .AddDownstreamWebApi("DownstreamApi", Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); #else - services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAdB2C"); + .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C")); #endif #endif #if (OrganizationalAuth) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.json index c0dd4d1ab9..634fb6db8a 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.json @@ -17,10 +17,10 @@ // }, ////#elseif (OrganizationalAuth) // "AzureAd": { -//#if (MultiOrgAuth) -// "Instance": "https:////login.microsoftonline.com/common", -//#elseif (SingleOrgAuth) // "Instance": "https:////login.microsoftonline.com/", +//#if (MultiOrgAuth) +// "TenantId": "common", +//#elseif (SingleOrgAuth) // "Domain": "qualified.domain.name", // "TenantId": "22222222-2222-2222-2222-222222222222", //#endif @@ -34,16 +34,16 @@ // }, ////#endif ////#if (GenerateApiOrGraph) -// "CalledApi": { +// "DownstreamApi": { // /* -// 'CalledApiScopes' contains space separated scopes of the Web API you want to call. This can be: +// 'Scopes' contains space separated scopes of the Web API you want to call. This can be: // - a scope for a V2 application (for instance api://b3682cc7-8b30-4bd2-aaba-080c6bf0fd31/access_as_user) // - a scope corresponding to a V1 application (for instance /.default, where is the // App ID URI of a legacy v1 Web application // Applications are registered in the https://portal.azure.com portal. // */ -// "CalledApiScopes": "user.read", -// "CalledApiUrl": "[WebApiUrl]" +// "BaseUrl": "[WebApiUrl]", +// "Scopes": "user.read" // }, ////#endif ////#if (IndividualLocalAuth) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json index f0e86147b2..ac54ee3f91 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json @@ -90,19 +90,6 @@ "exclude": [ "Data/SqlServer/**" ] - }, - { - "condition": "(!GenerateApi)", - "exclude": [ - "Services/DownstreamWebApi.cs" - ] - }, - { - "condition": "(!GenerateGraph)", - "exclude": [ - "Services/MicrosoftGraphServiceExtensions.cs", - "Services/TokenAcquisitionCredentialProvider.cs" - ] } ] } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Controllers/HomeController.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Controllers/HomeController.cs index cd05892b06..4aea265a2e 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Controllers/HomeController.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Controllers/HomeController.cs @@ -38,11 +38,20 @@ namespace Company.WebApplication1.Controllers _downstreamWebApi = downstreamWebApi; } - [AuthorizeForScopes(ScopeKeySection = "CalledApi:CalledApiScopes")] + [AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")] public async Task Index() { - ViewData["ApiResult"] = await _downstreamWebApi.CallWebApiAsync(); - + using var response = await _downstreamWebApi.CallWebApiForUserAsync("DownstreamApi").ConfigureAwait(false); + if (response.StatusCode == System.Net.HttpStatusCode.OK) + { + var apiResult = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + ViewData["ApiResult"] = apiResult; + } + else + { + var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new HttpRequestException($"Invalid status code in the HttpResponseMessage: {response.StatusCode}: {error}"); + } return View(); } #elseif (GenerateGraph) @@ -55,7 +64,7 @@ namespace Company.WebApplication1.Controllers _graphServiceClient = graphServiceClient; } - [AuthorizeForScopes(ScopeKeySection = "CalledApi:CalledApiScopes")] + [AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")] public async Task Index() { var user = await _graphServiceClient.Me.Request().GetAsync(); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Services/DownstreamWebApi.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Services/DownstreamWebApi.cs deleted file mode 100644 index 4806ca192d..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Services/DownstreamWebApi.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Identity.Web; - -namespace Company.WebApplication1 -{ - public interface IDownstreamWebApi - { - Task CallWebApiAsync(string relativeEndpoint = "", string[] requiredScopes = null); - } - - public static class DownstreamWebApiExtensions - { - public static void AddDownstreamWebApiService(this IServiceCollection services, IConfiguration configuration) - { - // https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests - services.AddHttpClient(); - } - } - - public class DownstreamWebApi : IDownstreamWebApi - { - private readonly ITokenAcquisition _tokenAcquisition; - - private readonly IConfiguration _configuration; - - private readonly HttpClient _httpClient; - - public DownstreamWebApi( - ITokenAcquisition tokenAcquisition, - IConfiguration configuration, - HttpClient httpClient) - { - _tokenAcquisition = tokenAcquisition; - _configuration = configuration; - _httpClient = httpClient; - } - - /// - /// Calls the Web API with the required scopes - /// - /// [Optional] Scopes required to call the Web API. If - /// not specified, uses scopes from the configuration - /// Endpoint relative to the CalledApiUrl configuration - /// A JSON string representing the result of calling the Web API - public async Task CallWebApiAsync(string relativeEndpoint = "", string[] requiredScopes = null) - { - string[] scopes = requiredScopes ?? _configuration["CalledApi:CalledApiScopes"]?.Split(' '); - string apiUrl = (_configuration["CalledApi:CalledApiUrl"] as string)?.TrimEnd('/') + $"/{relativeEndpoint}"; - - string accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes); - HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, apiUrl); - httpRequestMessage.Headers.Add("Authorization", $"bearer {accessToken}"); - - string apiResult; - var response = await _httpClient.SendAsync(httpRequestMessage); - if (response.StatusCode == HttpStatusCode.OK) - { - apiResult = await response.Content.ReadAsStringAsync(); - } - else - { - apiResult = $"Error calling the API '{apiUrl}'"; - } - - return apiResult; - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Services/MicrosoftGraphServiceExtensions.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Services/MicrosoftGraphServiceExtensions.cs deleted file mode 100644 index 2e805a9688..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Services/MicrosoftGraphServiceExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Graph; -using Microsoft.Identity.Web; - -namespace Company.WebApplication1 -{ - public static class MicrosoftGraphServiceExtensions - { - /// - /// Adds the Microsoft Graph client as a singleton. - /// - /// Service collection. - /// Initial scopes. - /// Base URL for Microsoft graph. This can be - /// changed for instance for applications running in national clouds - public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services, - IEnumerable initialScopes, - string graphBaseUrl = "https://graph.microsoft.com/v1.0") - { - services.AddTokenAcquisition(true); - services.AddSingleton(serviceProvider => - { - var tokenAquisitionService = serviceProvider.GetService(); - GraphServiceClient client = string.IsNullOrWhiteSpace(graphBaseUrl) ? - new GraphServiceClient(new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)) : - new GraphServiceClient(graphBaseUrl, new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)); - return client; - }); - - return services; - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Services/TokenAcquisitionCredentialProvider.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Services/TokenAcquisitionCredentialProvider.cs deleted file mode 100644 index 8042149d82..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Services/TokenAcquisitionCredentialProvider.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.Graph; -using Microsoft.Identity.Web; -using System.Collections; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Company.WebApplication1 -{ - internal class TokenAcquisitionCredentialProvider : IAuthenticationProvider - { - public TokenAcquisitionCredentialProvider(ITokenAcquisition tokenAcquisition, IEnumerable initialScopes) - { - _tokenAcquisition = tokenAcquisition; - _initialScopes = initialScopes; - } - - ITokenAcquisition _tokenAcquisition; - IEnumerable _initialScopes; - - public async Task AuthenticateRequestAsync(HttpRequestMessage request) - { - request.Headers.Add("Authorization", - $"Bearer {await _tokenAcquisition.GetAccessTokenForUserAsync(_initialScopes)}"); - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Startup.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Startup.cs index f0d19f0712..1308fe4cab 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Startup.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Startup.cs @@ -4,20 +4,10 @@ using System.Linq; using System.Threading.Tasks; #if (OrganizationalAuth || IndividualB2CAuth) using Microsoft.AspNetCore.Authentication; -#endif -#if (OrganizationalAuth) -using Microsoft.Identity.Web; -using Microsoft.Identity.Web.UI; -using Microsoft.Identity.Web.TokenCacheProviders.InMemory; -#if (MultiOrgAuth) using Microsoft.AspNetCore.Authentication.OpenIdConnect; -#endif using Microsoft.AspNetCore.Authorization; -#endif -#if (IndividualB2CAuth) using Microsoft.Identity.Web; using Microsoft.Identity.Web.UI; -using Microsoft.Identity.Web.TokenCacheProviders.InMemory; #endif using Microsoft.AspNetCore.Builder; #if (IndividualLocalAuth) @@ -74,28 +64,36 @@ namespace Company.WebApplication1 .AddEntityFrameworkStores(); #elif (OrganizationalAuth) #if (GenerateApiOrGraph) - services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd") - .AddMicrosoftWebAppCallsWebApi(Configuration, "AzureAd") - .AddInMemoryTokenCaches(); -#else - services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd"); + var initialScopes = Configuration.GetValue("DownstreamApi:Scopes")?.Split(' '); + #endif + services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) +#if (GenerateApiOrGraph) + .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) #if (GenerateApi) - services.AddDownstreamWebApiService(Configuration); + .AddDownstreamWebApi("DownstreamApi", Configuration.GetSection("DownstreamApi")) #endif #if (GenerateGraph) - services.AddMicrosoftGraph(Configuration.GetValue("CalledApi:CalledApiScopes")?.Split(' '), - Configuration.GetValue("CalledApi:CalledApiUrl")); + .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi")) +#endif + .AddInMemoryTokenCaches(); +#else + .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd")); #endif #elif (IndividualB2CAuth) #if (GenerateApi) - services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAdB2C") - .AddMicrosoftWebAppCallsWebApi(Configuration, "AzureAdB2C") - .AddInMemoryTokenCaches(); + var initialScopes = Configuration.GetValue("DownstreamApi:Scopes")?.Split(' '); - services.AddDownstreamWebApiService(Configuration); +#endif + services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) +#if (GenerateApi) + .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) + .AddDownstreamWebApi("DownstreamApi", Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); #else - services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAdB2C"); + .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C")); #endif #endif #if (OrganizationalAuth) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.json index f72b1ef631..f6582afb07 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.json @@ -17,10 +17,10 @@ // }, ////#elseif (OrganizationalAuth) // "AzureAd": { -//#if (MultiOrgAuth) -// "Instance": "https:////login.microsoftonline.com/common", -//#elseif (SingleOrgAuth) // "Instance": "https:////login.microsoftonline.com/", +//#if (MultiOrgAuth) +// "TenantId": "common", +//#elseif (SingleOrgAuth) // "Domain": "qualified.domain.name", // "TenantId": "22222222-2222-2222-2222-222222222222", //#endif @@ -34,16 +34,16 @@ // }, ////#endif ////#if (GenerateApiOrGraph) -// "CalledApi": { +// "DownstreamApi": { // /* -// 'CalledApiScopes' contains space separated scopes of the Web API you want to call. This can be: +// 'Scopes' contains space separated scopes of the Web API you want to call. This can be: // - a scope for a V2 application (for instance api://b3682cc7-8b30-4bd2-aaba-080c6bf0fd31/access_as_user) // - a scope corresponding to a V1 application (for instance /.default, where is the // App ID URI of a legacy v1 Web application // Applications are registered in the https://portal.azure.com portal. // */ -// "CalledApiScopes": "user.read", -// "CalledApiUrl": "[WebApiUrl]" +// "BaseUrl": "[WebApiUrl]", +// "Scopes": "user.read" // }, ////#endif ////#if (IndividualLocalAuth) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json index af0458974c..b16a1f9a27 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json @@ -32,6 +32,10 @@ "longName": "tenant-id", "shortName": "" }, + "DefaultScope": { + "longName": "default-scope", + "shortName": "" + }, "Framework": { "longName": "framework" }, diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json index f11b772861..78260944d2 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json @@ -36,19 +36,6 @@ "exclude": [ "Properties/launchSettings.json" ] - }, - { - "condition": "(!GenerateApi)", - "exclude": [ - "Services/DownstreamWebApi.cs" - ] - }, - { - "condition": "(!GenerateGraph)", - "exclude": [ - "Services/MicrosoftGraphServiceExtensions.cs", - "Services/TokenAcquisitionCredentialProvider.cs" - ] } ] } @@ -111,6 +98,13 @@ "replaces": "qualified.domain.name", "description": "The domain for the directory tenant (use with SingleOrg or IndividualB2C auth)." }, + "DefaultScope": { + "type": "parameter", + "datatype": "string", + "replaces": "api-scope", + "defaultValue": "access_as_user", + "description": "The API scope the client needs to request to provision an access token. (use with IndividualB2C, SingleOrg)." + }, "TenantId": { "type": "parameter", "datatype": "string", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Controllers/WeatherForecastController.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Controllers/WeatherForecastController.cs index 584c64869c..0cf62870ff 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Controllers/WeatherForecastController.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Controllers/WeatherForecastController.cs @@ -36,8 +36,8 @@ namespace Company.WebApplication1.Controllers private readonly ILogger _logger; - // The Web API will only accept tokens 1) for users, and 2) having the access_as_user scope for this API - static readonly string[] scopeRequiredByApi = new string[] { "access_as_user" }; + // The Web API will only accept tokens 1) for users, and 2) having the "api-scope" scope for this API + static readonly string[] scopeRequiredByApi = new string[] { "api-scope" }; #if (GenerateApi) private readonly IDownstreamWebApi _downstreamWebApi; @@ -54,7 +54,17 @@ namespace Company.WebApplication1.Controllers { HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi); - string downstreamApiResult = await _downstreamWebApi.CallWebApiAsync(); + using var response = await _downstreamWebApi.CallWebApiForUserAsync("DownstreamApi").ConfigureAwait(false); + if (response.StatusCode == System.Net.HttpStatusCode.OK) + { + var apiResult = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + // Do something + } + else + { + var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new HttpRequestException($"Invalid status code in the HttpResponseMessage: {response.StatusCode}: {error}"); + } var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Services/DownstreamWebApi.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Services/DownstreamWebApi.cs deleted file mode 100644 index 4806ca192d..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Services/DownstreamWebApi.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Identity.Web; - -namespace Company.WebApplication1 -{ - public interface IDownstreamWebApi - { - Task CallWebApiAsync(string relativeEndpoint = "", string[] requiredScopes = null); - } - - public static class DownstreamWebApiExtensions - { - public static void AddDownstreamWebApiService(this IServiceCollection services, IConfiguration configuration) - { - // https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests - services.AddHttpClient(); - } - } - - public class DownstreamWebApi : IDownstreamWebApi - { - private readonly ITokenAcquisition _tokenAcquisition; - - private readonly IConfiguration _configuration; - - private readonly HttpClient _httpClient; - - public DownstreamWebApi( - ITokenAcquisition tokenAcquisition, - IConfiguration configuration, - HttpClient httpClient) - { - _tokenAcquisition = tokenAcquisition; - _configuration = configuration; - _httpClient = httpClient; - } - - /// - /// Calls the Web API with the required scopes - /// - /// [Optional] Scopes required to call the Web API. If - /// not specified, uses scopes from the configuration - /// Endpoint relative to the CalledApiUrl configuration - /// A JSON string representing the result of calling the Web API - public async Task CallWebApiAsync(string relativeEndpoint = "", string[] requiredScopes = null) - { - string[] scopes = requiredScopes ?? _configuration["CalledApi:CalledApiScopes"]?.Split(' '); - string apiUrl = (_configuration["CalledApi:CalledApiUrl"] as string)?.TrimEnd('/') + $"/{relativeEndpoint}"; - - string accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes); - HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, apiUrl); - httpRequestMessage.Headers.Add("Authorization", $"bearer {accessToken}"); - - string apiResult; - var response = await _httpClient.SendAsync(httpRequestMessage); - if (response.StatusCode == HttpStatusCode.OK) - { - apiResult = await response.Content.ReadAsStringAsync(); - } - else - { - apiResult = $"Error calling the API '{apiUrl}'"; - } - - return apiResult; - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Services/MicrosoftGraphServiceExtensions.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Services/MicrosoftGraphServiceExtensions.cs deleted file mode 100644 index 2e805a9688..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Services/MicrosoftGraphServiceExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Graph; -using Microsoft.Identity.Web; - -namespace Company.WebApplication1 -{ - public static class MicrosoftGraphServiceExtensions - { - /// - /// Adds the Microsoft Graph client as a singleton. - /// - /// Service collection. - /// Initial scopes. - /// Base URL for Microsoft graph. This can be - /// changed for instance for applications running in national clouds - public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services, - IEnumerable initialScopes, - string graphBaseUrl = "https://graph.microsoft.com/v1.0") - { - services.AddTokenAcquisition(true); - services.AddSingleton(serviceProvider => - { - var tokenAquisitionService = serviceProvider.GetService(); - GraphServiceClient client = string.IsNullOrWhiteSpace(graphBaseUrl) ? - new GraphServiceClient(new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)) : - new GraphServiceClient(graphBaseUrl, new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)); - return client; - }); - - return services; - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Services/TokenAcquisitionCredentialProvider.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Services/TokenAcquisitionCredentialProvider.cs deleted file mode 100644 index 8042149d82..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Services/TokenAcquisitionCredentialProvider.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.Graph; -using Microsoft.Identity.Web; -using System.Collections; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Company.WebApplication1 -{ - internal class TokenAcquisitionCredentialProvider : IAuthenticationProvider - { - public TokenAcquisitionCredentialProvider(ITokenAcquisition tokenAcquisition, IEnumerable initialScopes) - { - _tokenAcquisition = tokenAcquisition; - _initialScopes = initialScopes; - } - - ITokenAcquisition _tokenAcquisition; - IEnumerable _initialScopes; - - public async Task AuthenticateRequestAsync(HttpRequestMessage request) - { - request.Headers.Add("Authorization", - $"Bearer {await _tokenAcquisition.GetAccessTokenForUserAsync(_initialScopes)}"); - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Startup.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Startup.cs index 0e935d107a..e1fedd249a 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Startup.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Startup.cs @@ -10,14 +10,8 @@ using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.AspNetCore.Mvc; #if (OrganizationalAuth || IndividualB2CAuth) using Microsoft.AspNetCore.Authentication; -#endif -#if (OrganizationalAuth) +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Identity.Web; -using Microsoft.Identity.Web.TokenCacheProviders.InMemory; -#endif -#if (IndividualB2CAuth) -using Microsoft.Identity.Web; -using Microsoft.Identity.Web.TokenCacheProviders.InMemory; #endif using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -45,32 +39,29 @@ namespace Company.WebApplication1 public void ConfigureServices(IServiceCollection services) { #if (OrganizationalAuth) + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) #if (GenerateApiOrGraph) - // Adds Microsoft Identity platform (AAD v2.0) support to protect this Api - services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAd") - .AddMicrosoftWebApiCallsWebApi(Configuration, "AzureAd") - .AddInMemoryTokenCaches(); - -#else - // Adds Microsoft Identity platform (AAD v2.0) support to protect this Api - services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAd"); -#endif + .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi() #if (GenerateApi) - services.AddDownstreamWebApiService(Configuration); + .AddDownstreamWebApi("DownstreamApi", Configuration.GetSection("DownstreamApi")) #endif #if (GenerateGraph) - services.AddMicrosoftGraph(Configuration.GetValue("CalledApi:CalledApiScopes")?.Split(' '), - Configuration.GetValue("CalledApi:CalledApiUrl")); + .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi")) +#endif + .AddInMemoryTokenCaches(); +#else + .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd")); #endif #elif (IndividualB2CAuth) + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) #if (GenerateApi) - services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAdB2C") - .AddMicrosoftWebApiCallsWebApi(Configuration, "AzureAdB2C") - .AddInMemoryTokenCaches(); - - services.AddDownstreamWebApiService(Configuration); + .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi() + .AddDownstreamWebApi("DownstreamApi", Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); #else - services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAdB2C"); + .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAdB2C")); #endif #endif @@ -98,6 +89,7 @@ namespace Company.WebApplication1 app.UseHttpsRedirection(); #endif + app.UseRouting(); #if (OrganizationalAuth || IndividualAuth) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/appsettings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/appsettings.json index cd6a5721b6..2b965fcc65 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/appsettings.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/appsettings.json @@ -13,10 +13,10 @@ // }, ////#elseif (OrganizationalAuth) // "AzureAd": { -//#if (!SingleOrgAuth) -// "Instance": "https:////login.microsoftonline.com/common", -//#else // "Instance": "https:////login.microsoftonline.com/", +//#if (!SingleOrgAuth) +// "TenantId": "common", +//#else // "Domain": "qualified.domain.name", // "TenantId": "22222222-2222-2222-2222-222222222222", //#endif @@ -31,16 +31,16 @@ // }, ////#endif ////#if (GenerateApiOrGraph) -// "CalledApi": { +// "DownstreamAPI": { // /* -// 'CalledApiScopes' contains space separated scopes of the Web API you want to call. This can be: +// 'Scopes' contains space separated scopes of the Web API you want to call. This can be: // - a scope for a V2 application (for instance api://b3682cc7-8b30-4bd2-aaba-080c6bf0fd31/access_as_user) // - a scope corresponding to a V1 application (for instance /.default, where is the // App ID URI of a legacy v1 Web application // Applications are registered in the https://portal.azure.com portal. // */ -// "CalledApiScopes": "user.read", -// "CalledApiUrl": "[WebApiUrl]" +// "BaseUrl": "[WebApiUrl]", +// "Scopes": "user.read" // }, ////#endif "Logging": {