Updating Web project templates to ms.id.web 0.3.0-preview (#25309)

* Updating Web project templates to ms.id.web 0.3.0-preview

* version bump
and quick fix to the Blasorwasm default values for
the Web api so that customers have a better experience

* Microsoft.Graph is brought in transitively by MS.ID.Web

Co-authored-by: John Luo <johluo@microsoft.com>
This commit is contained in:
Jean-Marc Prieur 2020-08-29 04:56:37 +02:00 committed by GitHub
parent f82794a6cb
commit 744e96b23d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 213 additions and 901 deletions

View File

@ -168,7 +168,6 @@ and are generated based on the last package release.
<LatestPackageReference Include="MessagePackAnalyzer" />
<LatestPackageReference Include="Microsoft.Data.SqlClient" />
<LatestPackageReference Include="Microsoft.Identity.Web.UI" />
<LatestPackageReference Include="Microsoft.Graph" />
<LatestPackageReference Include="Mono.Cecil" />
<LatestPackageReference Include="Moq" />
<LatestPackageReference Include="Newtonsoft.Json.Bson" />

View File

@ -260,9 +260,8 @@
<IdentityServer4StoragePackageVersion>4.0.4</IdentityServer4StoragePackageVersion>
<IdentityServer4EntityFrameworkStoragePackageVersion>4.0.4</IdentityServer4EntityFrameworkStoragePackageVersion>
<MessagePackPackageVersion>2.1.90</MessagePackPackageVersion>
<MicrosoftIdentityWebPackageVersion>0.2.3-preview</MicrosoftIdentityWebPackageVersion>
<MicrosoftIdentityWebUIPackageVersion>0.2.3-preview</MicrosoftIdentityWebUIPackageVersion>
<MicrosoftGraphPackageVersion>3.8.0</MicrosoftGraphPackageVersion>
<MicrosoftIdentityWebPackageVersion>0.3.1-preview</MicrosoftIdentityWebPackageVersion>
<MicrosoftIdentityWebUIPackageVersion>0.3.1-preview</MicrosoftIdentityWebUIPackageVersion>
<MessagePackAnalyzerPackageVersion>$(MessagePackPackageVersion)</MessagePackAnalyzerPackageVersion>
<MoqPackageVersion>4.10.0</MoqPackageVersion>
<MonoCecilPackageVersion>0.11.2</MonoCecilPackageVersion>

View File

@ -25,7 +25,6 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="${MicrosoftEntityFrameworkCoreToolsPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
<PackageReference Include="Microsoft.Identity.Web" Version="${MicrosoftIdentityWebPackageVersion}" Condition=" '$(IndividualB2CAuth)' == 'True' OR '$(OrganizationalAuth)' == 'True'" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="${MicrosoftIdentityWebUIPackageVersion}" Condition=" '$(IndividualB2CAuth)' == 'True' OR '$(OrganizationalAuth)' == 'True'" />
<PackageReference Include="Microsoft.Graph" Version="${MicrosoftGraphPackageVersion}" Condition=" '$(GenerateGraph)' == 'True' "/>
</ItemGroup>
<!--#endif -->

View File

@ -40,7 +40,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Identity.Web" Version="${MicrosoftIdentityWebPackageVersion}" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="${MicrosoftIdentityWebUIPackageVersion}" />
<PackageReference Include="Microsoft.Graph" Version="${MicrosoftGraphPackageVersion}" Condition=" '$(GenerateGraph)' == 'True' "/>
</ItemGroup>
<!--#endif -->

View File

@ -25,7 +25,6 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="${MicrosoftEntityFrameworkCoreToolsPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
<PackageReference Include="Microsoft.Identity.Web" Version="${MicrosoftIdentityWebPackageVersion}" Condition=" '$(IndividualB2CAuth)' == 'True' OR '$(OrganizationalAuth)' == 'True'" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="${MicrosoftIdentityWebUIPackageVersion}" Condition=" '$(IndividualB2CAuth)' == 'True' OR '$(OrganizationalAuth)' == 'True'" />
<PackageReference Include="Microsoft.Graph" Version="${MicrosoftGraphPackageVersion}" Condition=" '$(GenerateGraph)' == 'True' "/>
</ItemGroup>
<!--#endif -->

View File

@ -25,7 +25,6 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="${MicrosoftEntityFrameworkCoreToolsPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
<PackageReference Include="Microsoft.Identity.Web" Version="${MicrosoftIdentityWebPackageVersion}" Condition=" '$(IndividualB2CAuth)' == 'True' OR '$(OrganizationalAuth)' == 'True'" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="${MicrosoftIdentityWebUIPackageVersion}" Condition=" '$(IndividualB2CAuth)' == 'True' OR '$(OrganizationalAuth)' == 'True'" />
<PackageReference Include="Microsoft.Graph" Version="${MicrosoftGraphPackageVersion}" Condition=" '$(GenerateGraph)' == 'True' "/>
</ItemGroup>
<!--#endif -->

View File

@ -10,7 +10,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Identity.Web" Version="${MicrosoftIdentityWebPackageVersion}" Condition="'$(OrganizationalAuth)' == 'True' OR '$(IndividualB2CAuth)' == 'True'"/>
<PackageReference Include="Microsoft.Graph" Version="${MicrosoftGraphPackageVersion}" Condition="'$(GenerateGraph)' == 'True'"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="${SwashbuckleAspNetCorePackageVersion}" Condition="'$(EnableOpenAPI)' == 'True'" />
</ItemGroup>
<!--#endif -->

View File

@ -138,8 +138,6 @@
{
"condition": "(!GenerateGraph)",
"exclude": [
"Services/MicrosoftGraphServiceExtensions.cs",
"Services/TokenAcquisitionCredentialProvider.cs",
"Shared/NavMenu.CallsMicrosoftGraph.razor",
"Pages/ShowProfile.razor"
]

View File

@ -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)
{

View File

@ -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<string> 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<IDownstreamWebApi, DownstreamWebApi>();
}
}
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;
}
/// <summary>
/// Calls the Web API with the required scopes
/// </summary>
/// <param name="requireScopes">[Optional] Scopes required to call the Web API. If
/// not specified, uses scopes from the configuration</param>
/// <param name="relativeEndpoint">Endpoint relative to the CalledApiUrl configuration</param>
/// <returns>A JSON string representing the result of calling the Web API</returns>
public async Task<string> 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;
}
}
}

View File

@ -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
{
/// <summary>
/// Adds the Microsoft Graph client as a singleton.
/// </summary>
/// <param name="services">Service collection.</param>
/// <param name="initialScopes">Initial scopes.</param>
/// <param name="graphBaseUrl">Base URL for Microsoft graph. This can be
/// changed for instance for applications running in national clouds</param>
public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services,
IEnumerable<string> initialScopes,
string graphBaseUrl = "https://graph.microsoft.com/v1.0")
{
services.AddTokenAcquisition(true);
services.AddSingleton<GraphServiceClient, GraphServiceClient>(serviceProvider =>
{
var tokenAquisitionService = serviceProvider.GetService<ITokenAcquisition>();
GraphServiceClient client = string.IsNullOrWhiteSpace(graphBaseUrl) ?
new GraphServiceClient(new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)) :
new GraphServiceClient(graphBaseUrl, new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes));
return client;
});
return services;
}
}
}

View File

@ -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<string> initialScopes)
{
_tokenAcquisition = tokenAcquisition;
_initialScopes = initialScopes;
}
ITokenAcquisition _tokenAcquisition;
IEnumerable<string> _initialScopes;
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
request.Headers.Add("Authorization",
$"Bearer {await _tokenAcquisition.GetAccessTokenForUserAsync(_initialScopes)}");
}
}
}

View File

@ -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<ApplicationDbContext>();
#elif (OrganizationalAuth)
#if (GenerateApiOrGraph)
string[] scopes = Configuration.GetValue<string>("CalledApi:CalledApiScopes")?.Split(' ');
var initialScopes = Configuration.GetValue<string>("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<string>("CalledApi:CalledApiUrl"));
.AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
#endif
.AddInMemoryTokenCaches();
#else
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"));
#endif
#elif (IndividualB2CAuth)
#if (GenerateApi)
string[] scopes = Configuration.GetValue<string>("CalledApi:CalledApiScopes")?.Split(' ');
#endif
#if (GenerateApi)
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAdB2C")
.AddMicrosoftWebAppCallsWebApi(Configuration, scopes, "AzureAdB2C")
.AddInMemoryTokenCaches();
var initialScopes = Configuration.GetValue<string>("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)

View File

@ -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 <App ID URI>/.default, where <App ID URI> 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)

View File

@ -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",

View File

@ -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<WeatherForecastController> _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<IEnumerable<WeatherForecast>> Get()

View File

@ -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<string> 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<IDownstreamWebApi, DownstreamWebApi>();
}
}
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;
}
/// <summary>
/// Calls the Web API with the required scopes
/// </summary>
/// <param name="requireScopes">[Optional] Scopes required to call the Web API. If
/// not specified, uses scopes from the configuration</param>
/// <param name="relativeEndpoint">Endpoint relative to the CalledApiUrl configuration</param>
/// <returns>A JSON string representing the result of calling the Web API</returns>
public async Task<string> 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;
}
}
}

View File

@ -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
{
/// <summary>
/// Adds the Microsoft Graph client as a singleton.
/// </summary>
/// <param name="services">Service collection.</param>
/// <param name="initialScopes">Initial scopes.</param>
/// <param name="graphBaseUrl">Base URL for Microsoft graph. This can be
/// changed for instance for applications running in national clouds</param>
public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services,
IEnumerable<string> initialScopes,
string graphBaseUrl = "https://graph.microsoft.com/v1.0")
{
services.AddTokenAcquisition(true);
services.AddSingleton<GraphServiceClient, GraphServiceClient>(serviceProvider =>
{
var tokenAquisitionService = serviceProvider.GetService<ITokenAcquisition>();
GraphServiceClient client = string.IsNullOrWhiteSpace(graphBaseUrl) ?
new GraphServiceClient(new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)) :
new GraphServiceClient(graphBaseUrl, new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes));
return client;
});
return services;
}
}
}

View File

@ -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<string> initialScopes)
{
_tokenAcquisition = tokenAcquisition;
_initialScopes = initialScopes;
}
ITokenAcquisition _tokenAcquisition;
IEnumerable<string> _initialScopes;
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
request.Headers.Add("Authorization",
$"Bearer {await _tokenAcquisition.GetAccessTokenForUserAsync(_initialScopes)}");
}
}
}

View File

@ -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<string>("CalledApi:CalledApiScopes")?.Split(' '),
Configuration.GetValue<string>("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

View File

@ -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 <App ID URI>/.default, where <App ID URI> 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": {

View File

@ -94,19 +94,6 @@
"exclude": [
"Data/SqlServer/**"
]
},
{
"condition": "(!GenerateApi)",
"exclude": [
"Services/DownstreamWebApi.cs"
]
},
{
"condition": "(!GenerateGraph)",
"exclude": [
"Services/MicrosoftGraphServiceExtensions.cs",
"Services/TokenAcquisitionCredentialProvider.cs"
]
}
]
}

View File

@ -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;

View File

@ -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<string> 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<IDownstreamWebApi, DownstreamWebApi>();
}
}
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;
}
/// <summary>
/// Calls the Web API with the required scopes
/// </summary>
/// <param name="requireScopes">[Optional] Scopes required to call the Web API. If
/// not specified, uses scopes from the configuration</param>
/// <param name="relativeEndpoint">Endpoint relative to the CalledApiUrl configuration</param>
/// <returns>A JSON string representing the result of calling the Web API</returns>
public async Task<string> 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;
}
}
}

View File

@ -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
{
/// <summary>
/// Adds the Microsoft Graph client as a singleton.
/// </summary>
/// <param name="services">Service collection.</param>
/// <param name="initialScopes">Initial scopes.</param>
/// <param name="graphBaseUrl">Base URL for Microsoft graph. This can be
/// changed for instance for applications running in national clouds</param>
public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services,
IEnumerable<string> initialScopes,
string graphBaseUrl = "https://graph.microsoft.com/v1.0")
{
services.AddTokenAcquisition(true);
services.AddSingleton<GraphServiceClient, GraphServiceClient>(serviceProvider =>
{
var tokenAquisitionService = serviceProvider.GetService<ITokenAcquisition>();
GraphServiceClient client = string.IsNullOrWhiteSpace(graphBaseUrl) ?
new GraphServiceClient(new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)) :
new GraphServiceClient(graphBaseUrl, new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes));
return client;
});
return services;
}
}
}

View File

@ -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<string> initialScopes)
{
_tokenAcquisition = tokenAcquisition;
_initialScopes = initialScopes;
}
ITokenAcquisition _tokenAcquisition;
IEnumerable<string> _initialScopes;
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
request.Headers.Add("Authorization",
$"Bearer {await _tokenAcquisition.GetAccessTokenForUserAsync(_initialScopes)}");
}
}
}

View File

@ -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<ApplicationDbContext>();
#elif (OrganizationalAuth)
#if (GenerateApiOrGraph)
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd")
.AddMicrosoftWebAppCallsWebApi(Configuration, "AzureAd")
.AddInMemoryTokenCaches();
#else
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd");
var initialScopes = Configuration.GetValue<string>("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<string>("CalledApi:CalledApiScopes")?.Split(' '),
Configuration.GetValue<string>("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<string>("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)

View File

@ -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 <App ID URI>/.default, where <App ID URI> 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)

View File

@ -90,19 +90,6 @@
"exclude": [
"Data/SqlServer/**"
]
},
{
"condition": "(!GenerateApi)",
"exclude": [
"Services/DownstreamWebApi.cs"
]
},
{
"condition": "(!GenerateGraph)",
"exclude": [
"Services/MicrosoftGraphServiceExtensions.cs",
"Services/TokenAcquisitionCredentialProvider.cs"
]
}
]
}

View File

@ -38,11 +38,20 @@ namespace Company.WebApplication1.Controllers
_downstreamWebApi = downstreamWebApi;
}
[AuthorizeForScopes(ScopeKeySection = "CalledApi:CalledApiScopes")]
[AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")]
public async Task<IActionResult> 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<IActionResult> Index()
{
var user = await _graphServiceClient.Me.Request().GetAsync();

View File

@ -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<string> 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<IDownstreamWebApi, DownstreamWebApi>();
}
}
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;
}
/// <summary>
/// Calls the Web API with the required scopes
/// </summary>
/// <param name="requireScopes">[Optional] Scopes required to call the Web API. If
/// not specified, uses scopes from the configuration</param>
/// <param name="relativeEndpoint">Endpoint relative to the CalledApiUrl configuration</param>
/// <returns>A JSON string representing the result of calling the Web API</returns>
public async Task<string> 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;
}
}
}

View File

@ -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
{
/// <summary>
/// Adds the Microsoft Graph client as a singleton.
/// </summary>
/// <param name="services">Service collection.</param>
/// <param name="initialScopes">Initial scopes.</param>
/// <param name="graphBaseUrl">Base URL for Microsoft graph. This can be
/// changed for instance for applications running in national clouds</param>
public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services,
IEnumerable<string> initialScopes,
string graphBaseUrl = "https://graph.microsoft.com/v1.0")
{
services.AddTokenAcquisition(true);
services.AddSingleton<GraphServiceClient, GraphServiceClient>(serviceProvider =>
{
var tokenAquisitionService = serviceProvider.GetService<ITokenAcquisition>();
GraphServiceClient client = string.IsNullOrWhiteSpace(graphBaseUrl) ?
new GraphServiceClient(new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)) :
new GraphServiceClient(graphBaseUrl, new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes));
return client;
});
return services;
}
}
}

View File

@ -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<string> initialScopes)
{
_tokenAcquisition = tokenAcquisition;
_initialScopes = initialScopes;
}
ITokenAcquisition _tokenAcquisition;
IEnumerable<string> _initialScopes;
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
request.Headers.Add("Authorization",
$"Bearer {await _tokenAcquisition.GetAccessTokenForUserAsync(_initialScopes)}");
}
}
}

View File

@ -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<ApplicationDbContext>();
#elif (OrganizationalAuth)
#if (GenerateApiOrGraph)
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd")
.AddMicrosoftWebAppCallsWebApi(Configuration, "AzureAd")
.AddInMemoryTokenCaches();
#else
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd");
var initialScopes = Configuration.GetValue<string>("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<string>("CalledApi:CalledApiScopes")?.Split(' '),
Configuration.GetValue<string>("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<string>("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)

View File

@ -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 <App ID URI>/.default, where <App ID URI> 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)

View File

@ -32,6 +32,10 @@
"longName": "tenant-id",
"shortName": ""
},
"DefaultScope": {
"longName": "default-scope",
"shortName": ""
},
"Framework": {
"longName": "framework"
},

View File

@ -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",

View File

@ -36,8 +36,8 @@ namespace Company.WebApplication1.Controllers
private readonly ILogger<WeatherForecastController> _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

View File

@ -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<string> 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<IDownstreamWebApi, DownstreamWebApi>();
}
}
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;
}
/// <summary>
/// Calls the Web API with the required scopes
/// </summary>
/// <param name="requireScopes">[Optional] Scopes required to call the Web API. If
/// not specified, uses scopes from the configuration</param>
/// <param name="relativeEndpoint">Endpoint relative to the CalledApiUrl configuration</param>
/// <returns>A JSON string representing the result of calling the Web API</returns>
public async Task<string> 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;
}
}
}

View File

@ -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
{
/// <summary>
/// Adds the Microsoft Graph client as a singleton.
/// </summary>
/// <param name="services">Service collection.</param>
/// <param name="initialScopes">Initial scopes.</param>
/// <param name="graphBaseUrl">Base URL for Microsoft graph. This can be
/// changed for instance for applications running in national clouds</param>
public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services,
IEnumerable<string> initialScopes,
string graphBaseUrl = "https://graph.microsoft.com/v1.0")
{
services.AddTokenAcquisition(true);
services.AddSingleton<GraphServiceClient, GraphServiceClient>(serviceProvider =>
{
var tokenAquisitionService = serviceProvider.GetService<ITokenAcquisition>();
GraphServiceClient client = string.IsNullOrWhiteSpace(graphBaseUrl) ?
new GraphServiceClient(new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)) :
new GraphServiceClient(graphBaseUrl, new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes));
return client;
});
return services;
}
}
}

View File

@ -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<string> initialScopes)
{
_tokenAcquisition = tokenAcquisition;
_initialScopes = initialScopes;
}
ITokenAcquisition _tokenAcquisition;
IEnumerable<string> _initialScopes;
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
request.Headers.Add("Authorization",
$"Bearer {await _tokenAcquisition.GetAccessTokenForUserAsync(_initialScopes)}");
}
}
}

View File

@ -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<string>("CalledApi:CalledApiScopes")?.Split(' '),
Configuration.GetValue<string>("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)

View File

@ -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 <App ID URI>/.default, where <App ID URI> 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": {