diff --git a/Microsoft.Identity.Web.sln b/Microsoft.Identity.Web.sln index 7cc5f018b..b36821967 100644 --- a/Microsoft.Identity.Web.sln +++ b/Microsoft.Identity.Web.sln @@ -323,9 +323,7 @@ Global {8C557C25-25C9-45B6-B2D3-77093893EBC5}.Release|Any CPU.ActiveCfg = Release|Any CPU {8C557C25-25C9-45B6-B2D3-77093893EBC5}.Release|Any CPU.Build.0 = Release|Any CPU {E3611BED-B43A-40BD-A0A3-98AD65B5DAA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E3611BED-B43A-40BD-A0A3-98AD65B5DAA4}.Debug|Any CPU.Build.0 = Debug|Any CPU {E3611BED-B43A-40BD-A0A3-98AD65B5DAA4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E3611BED-B43A-40BD-A0A3-98AD65B5DAA4}.Release|Any CPU.Build.0 = Release|Any CPU {98F57CC8-01A0-49F3-B859-DDC4F8F5CD2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {98F57CC8-01A0-49F3-B859-DDC4F8F5CD2F}.Debug|Any CPU.Build.0 = Debug|Any CPU {98F57CC8-01A0-49F3-B859-DDC4F8F5CD2F}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/ProjectTemplates/AspNetCoreMicrosoftIdentityWebProjectTemplates.csproj b/ProjectTemplates/AspNetCoreMicrosoftIdentityWebProjectTemplates.csproj index 4337149ad..899e2ba19 100644 --- a/ProjectTemplates/AspNetCoreMicrosoftIdentityWebProjectTemplates.csproj +++ b/ProjectTemplates/AspNetCoreMicrosoftIdentityWebProjectTemplates.csproj @@ -2,7 +2,7 @@ - 1.24.0 + 1.25.0 $(ClientSemVer) diff --git a/ProjectTemplates/test-aspnet-templates-from-nuget.bat b/ProjectTemplates/test-aspnet-templates-from-nuget.bat index ac975d0a8..e4bbff223 100644 --- a/ProjectTemplates/test-aspnet-templates-from-nuget.bat +++ b/ProjectTemplates/test-aspnet-templates-from-nuget.bat @@ -1,6 +1,6 @@ echo "Ensure ClientSemVer" if "%ClientSemVer%" == "" ( -set ClientSemVer=1.24.0 +set ClientSemVer=1.25.0 ) REM: This is to test Microsoft.Identity.Web templates diff --git a/ProjectTemplates/test-templates-from-nuget.bat b/ProjectTemplates/test-templates-from-nuget.bat index afc16aab4..8fccc6bb9 100644 --- a/ProjectTemplates/test-templates-from-nuget.bat +++ b/ProjectTemplates/test-templates-from-nuget.bat @@ -1,6 +1,6 @@ echo "Ensure ClientSemVer" if "%ClientSemVer%" == "" ( -set ClientSemVer=1.24.0 +set ClientSemVer=1.25.0 ) REM: This is to test Microsoft.Identity.Web templates diff --git a/ProjectTemplates/test-templates.bat b/ProjectTemplates/test-templates.bat index 51a9707f1..8f5ec5106 100644 --- a/ProjectTemplates/test-templates.bat +++ b/ProjectTemplates/test-templates.bat @@ -1,6 +1,6 @@ echo "Ensure ClientSemVer" if "%ClientSemVer%" == "" ( -set ClientSemVer=1.24.0 +set ClientSemVer=1.25.0 ) Set TemplateNugetPackageName="Microsoft.Identity.Web.ProjectTemplates" diff --git a/TESTING.md b/TESTING.md index 2a8b4eb27..a675955d9 100644 --- a/TESTING.md +++ b/TESTING.md @@ -34,7 +34,7 @@ In a Developer Command Prompt: 3. Set the version of the templates to test. - `Set ClientSemVer=1.24.0` + `Set ClientSemVer=1.25.0` 4. In ProjectTemplates open the Configuration.json file and add the client secrets (or your own config file) . @@ -73,7 +73,7 @@ In a Developer Command Prompt: 2. Set the version of the templates to test. - `Set ClientSemVer=1.24.0` + `Set ClientSemVer=1.25.0` 3. Add client secrets to the `ProjectTemplates\Configuration.json` file diff --git a/changelog.md b/changelog.md index 85f315203..c38b47cd0 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,18 @@ +1.25.0 +========== +### New Features: +**Microsoft.Identity.Web now supports checking for scopes or app permissions,** via the `RequestedScopeOrAppPermissionAttribute`. See issue [#1641](https://github.com/AzureAD/microsoft-identity-web/issues/1641) for details. +**Extend TokenAcquisitionTokenCredential concept to support tokens as app**. See issue [#1723](https://github.com/AzureAD/microsoft-identity-web/issues/1723) for details. + +### Bug Fixes: +**IJwtBearerMiddlewareDiagnostics is now transient and not a singleton**. See issue [#1710](https://github.com/AzureAD/microsoft-identity-web/issues/1710) for details. +**In web API scenario, use the `tid` claim of the incoming assertion, unless overridden**. See issue [#1738](https://github.com/AzureAD/microsoft-identity-web/issues/1738) for details. + +1.24.1 +========== +### Bug Fixes: +**Microsoft.Identity.Web now returns `TokenValidatedContext.Fail` instead of throwing `UnauthorizedAccessException` in case of missing roles or scopes**, which enables a better developer experience. See issue [#1716](https://github.com/AzureAD/microsoft-identity-web/issues/1716) for details. + 1.24.0 ========== Update to Microsoft.IdentityModel 6.17.0. diff --git a/src/Microsoft.Identity.Web.OWIN/Microsoft.Identity.Web.OWIN.csproj b/src/Microsoft.Identity.Web.OWIN/Microsoft.Identity.Web.OWIN.csproj index 7371f5adb..c5d4f129d 100644 --- a/src/Microsoft.Identity.Web.OWIN/Microsoft.Identity.Web.OWIN.csproj +++ b/src/Microsoft.Identity.Web.OWIN/Microsoft.Identity.Web.OWIN.csproj @@ -83,7 +83,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs index 9d2fd098a..b0a2193a5 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs @@ -654,11 +654,25 @@ private IConfidentialClientApplication BuildConfidentialClientApplication(Merged if (builder != null) { builder.WithSendX5C(mergedOptions.SendX5C); + + ClaimsPrincipal? user = _tokenAcquisitionHost.GetUserFromRequest(); + var userTenant = string.Empty; + if (user != null) + { + userTenant = user.GetTenantId(); + builder.WithCcsRoutingHint(user.GetObjectId(), userTenant); + } if (!string.IsNullOrEmpty(tenantId)) { builder.WithTenantId(tenantId); } - + else + { + if (!string.IsNullOrEmpty(userTenant)) + { + builder.WithTenantId(userTenant); + } + } if (tokenAcquisitionOptions != null) { builder.WithExtraQueryParameters(tokenAcquisitionOptions.ExtraQueryParameters); @@ -669,13 +683,7 @@ private IConfidentialClientApplication BuildConfidentialClientApplication(Merged { builder.WithProofOfPossession(tokenAcquisitionOptions.PoPConfiguration); } - } - - ClaimsPrincipal? user = _tokenAcquisitionHost.GetUserFromRequest(); - if (user != null) - { - builder.WithCcsRoutingHint(user.GetObjectId(), user.GetTenantId()); - } + } return await builder.ExecuteAsync(tokenAcquisitionOptions != null ? tokenAcquisitionOptions.CancellationToken : CancellationToken.None) .ConfigureAwait(false); diff --git a/src/Microsoft.Identity.Web/AzureSdkSupport/TokenAcquisitionAppTokenCredential.cs b/src/Microsoft.Identity.Web/AzureSdkSupport/TokenAcquisitionAppTokenCredential.cs new file mode 100644 index 000000000..c85cb17ca --- /dev/null +++ b/src/Microsoft.Identity.Web/AzureSdkSupport/TokenAcquisitionAppTokenCredential.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core; +using Microsoft.Identity.Client; + +namespace Microsoft.Identity.Web +{ + /// + /// Azure SDK token credential for App tokens based on the ITokenAcquisition service. + /// + public class TokenAcquisitionAppTokenCredential : TokenCredential + { + private ITokenAcquisition _tokenAcquisition; + + /// + /// Constructor from an ITokenAcquisition service. + /// + /// Token acquisition. + public TokenAcquisitionAppTokenCredential(ITokenAcquisition tokenAcquisition) + { + _tokenAcquisition = tokenAcquisition; + } + + /// + public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + AuthenticationResult result = _tokenAcquisition.GetAuthenticationResultForAppAsync(requestContext.Scopes.First()) + .GetAwaiter() + .GetResult(); + return new AccessToken(result.AccessToken, result.ExpiresOn); + } + + /// + public override async ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + AuthenticationResult result = await _tokenAcquisition.GetAuthenticationResultForAppAsync(requestContext.Scopes.First()).ConfigureAwait(false); + return new AccessToken(result.AccessToken, result.ExpiresOn); + } + } +} diff --git a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApi.cs b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApi.cs index 0a75b16b6..50a7b6d61 100644 --- a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApi.cs +++ b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApi.cs @@ -124,7 +124,11 @@ public async Task CallWebApiForUserAsync( { string error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); +#if DOTNET_50_AND_ABOVE + throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}", null, response.StatusCode); +#else throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}"); +#endif } string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); diff --git a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiGenericExtensions.cs b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiGenericExtensions.cs index 6bc5b5e47..660a4ffa9 100644 --- a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiGenericExtensions.cs +++ b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiGenericExtensions.cs @@ -294,7 +294,11 @@ private static StringContent ConvertFromInput(TInput input) { string error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); +#if DOTNET_50_AND_ABOVE + throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}", null, response.StatusCode); +#else throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}"); +#endif } string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); diff --git a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj index 486d38d8b..6d67b52a6 100644 --- a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj +++ b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj @@ -24,7 +24,7 @@ The release notes are available at https://github.com/AzureAD/microsoft-identity-web/releases and the roadmap at https://github.com/AzureAD/microsoft-identity-web/wiki#roadmap Microsoft Identity Web;Microsoft identity platform;Microsoft.Identity.Web;.NET;ASP.NET Core;Web App;Web API;B2C;Azure Active Directory;AAD;Identity;Authentication;Authorization {FD55C071-48D1-4FE8-8B1D-773E067FEC91} - 6.17.0 + 6.19.0 true diff --git a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml index 179ec7ff8..adf336a73 100644 --- a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml +++ b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml @@ -4,5 +4,3219 @@ Microsoft.Identity.Web + + + Extension methods for . + + + + + Creates the from the values found + in an . + + The instance. + A built from . + + + + Extension methods related to App Services authentication (Easy Auth). + + + + + Add authentication with App Services. + + Authentication builder. + The builder, to chain commands. + + + + Default values related to AppServiceAuthentication handler. + + + + + The default value used for AppServiceAuthenticationOptions.AuthenticationScheme. + + + + + App service authentication handler. + + + + + Constructor for the AppServiceAuthenticationHandler. + Note the parameters are required by the base class. + + App service authentication options. + Logger factory. + URL encoder. + System clock. + + + + + + + Information about the App Services configuration on the host. + + + + + Is App Services authentication enabled?. + + + + + Logout URL for App Services Auth web sites. + + + + + ClientID of the App Services Auth web site. + + + + + Client secret of the App Services Auth web site. + + + + + Issuer of the App Services Auth web site. + + + + + Get headers from environment to help debugging App Services authentication. + + + + + Get the ID token from the headers sent by App services authentication. + + Headers. + The ID Token. + + + + Get the IDP from the headers sent by App services authentication. + + Headers. + The IDP. + + + + Get the user claims from the headers and environment variables. + + Headers. + User claims. + + + + Options for Azure App Services authentication. + + + + + Implementation of ITokenAcquisition for App Services authentication (EasyAuth). + + + + + Please call GetOrCreateApplication instead of accessing this field directly. + + + + + Constructor of the AppServicesAuthenticationTokenAcquisition. + + The App token cache provider. + Access to the HttpContext of the request. + HTTP client factory. + + + + + + + + + + + + + + + + + + + + + + + + + Filter used on a controller action to trigger incremental consent. + + + The following controller action will trigger. + + [AuthorizeForScopes(Scopes = new[] {"Mail.Send"})] + public async Task<IActionResult> SendEmail() + { + } + + + + + + Scopes to request. + + + + + Key section on the configuration file that holds the scope value. + + + + + Azure AD B2C user flow. + + + + + Allows specifying an AuthenticationScheme if OpenIdConnect is not the default challenge scheme. + + + + + Handles the . + + Context provided by ASP.NET Core. + + + + Finds an MsalUiRequiredException in one of the inner exceptions. + + Exception from which we look for an MsalUiRequiredException. + The MsalUiRequiredException if there is one, null, otherwise. + + + + Extensions for . + + + + + Enables an Azure Function to act as/expose a protected web API, enabling bearer token authentication. Calling this method from your Azure function validates the token and exposes the identity of the user or app on behalf of which your function is called, in the HttpContext.User member, where your function can make use of it. + + The current HTTP Context, such as req.HttpContext. + A task indicating success or failure. In case of failure . + + + + Azure SDK token credential for App tokens based on the ITokenAcquisition service. + + + + + Constructor from an ITokenAcquisition service. + + Token acquisition. + + + + + + + + + + Azure SDK token credential based on the ITokenAcquisition service. + + + + + Constructor from an ITokenAcquisition service. + + Token acquisition. + + + + + + + + + + Factory class to create objects. + + + + + Instantiate a from an account object ID and tenant ID. This can + be useful when the web app subscribes to another service on behalf of the user + and then is called back by a notification where the user is identified by their tenant + ID and object ID (like in Microsoft Graph Web Hooks). + + Tenant ID of the account. + Object ID of the account in this tenant ID. + A containing these two claims. + + + + private async Task GetChangedMessagesAsync(IEnumerable<Notification> notifications) + { + HttpContext.User = ClaimsPrincipalExtension.FromTenantIdAndObjectId(subscription.TenantId, + subscription.UserId); + foreach (var notification in notifications) + { + SubscriptionStore subscription = + subscriptionStore.GetSubscriptionInfo(notification.SubscriptionId); + string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes); + ...} + } + + + + + + Constants for claim types. + + + + + Name claim: "name". + + + + + Old Object Id claim: http://schemas.microsoft.com/identity/claims/objectidentifier. + + + + + New Object id claim: "oid". + + + + + PreferredUserName: "preferred_username". + + + + + Old TenantId claim: "http://schemas.microsoft.com/identity/claims/tenantid". + + + + + New Tenant Id claim: "tid". + + + + + ClientInfo claim: "client_info". + + + + + UniqueObjectIdentifier: "uid". + Home Object Id. + + + + + UniqueTenantIdentifier: "utid". + Home Tenant Id. + + + + + Older scope claim: "http://schemas.microsoft.com/identity/claims/scope". + + + + + Newer scope claim: "scp". + + + + + New Roles claim = "roles". + + + + + Old Role claim: "http://schemas.microsoft.com/ws/2008/06/identity/claims/role". + + + + + Subject claim: "sub". + + + + + Acr claim: "acr". + + + + + UserFlow claim: "http://schemas.microsoft.com/claims/authnclassreference". + + + + + Tfp claim: "tfp". + + + + + Name Identifier ID claim: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier". + + + + + General constants for Microsoft Identity Web. + + + + + LoginHint. + Represents the preferred_username claim in the ID token. + + + + + DomainHint. + Determined by the tenant Id. + + + + + Claims. + Determined from the signed-in user. + + + + + Bearer. + Predominant type of access token used with OAuth 2.0. + + + + + AzureAd. + Configuration section name for AzureAd. + + + + + AzureAdB2C. + Configuration section name for AzureAdB2C. + + + + + Scope. + + + + + Policy for B2C user flows. + The name of the policy to check against a specific user flow. + + + + + SpaAuthCode. + Key to retreive SpaAuthCode from the HttpContext. + + + + + Constants related to the error messages. + + + + + Constants related to the log messages. + + + + + Extension class containing cookie policies (work around for same site). + + + + + Handles SameSite cookie issue according to the https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1. + The default list of user agents that disallow "SameSite=None", + was taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. + + to update. + to chain. + + + + Handles SameSite cookie issue according to the docs: https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1 + The default list of user agents that disallow "SameSite=None", was taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. + + to update. + If you don't want to use the default user agent list implementation, + the method sent in this parameter will be run against the user agent and if returned true, SameSite value will be set to Unspecified. + The default user agent list used can be found at: https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. + to chain. + + + + Checks if the specified user agent supports "SameSite=None" cookies. + + Browser user agent. + + Incompatible user agents include: + + Versions of Chrome from Chrome 51 to Chrome 66 (inclusive on both ends). + Versions of UC Browser on Android prior to version 12.13.2. + Versions of Safari and embedded browsers on MacOS 10.14 and all browsers on iOS 12. + + Reference: https://www.chromium.org/updates/same-site/incompatible-clients. + + True, if the user agent does not allow "SameSite=None" cookie; otherwise, false. + + + + The default implementation of . + + + + + Creates a new instance of . + + + The to resolve dependencies from. + + + + + + + + + + + Implementation for the downstream web API. + + + + + Constructor. + + Token acquisition service. + Named options provider. + HTTP client. + Configuration options. + + + + + + + + + + + + + Merge the options from configuration and override from caller. + + Named configuration. + Delegate to override the configuration. + + + + Extension methods to support downstream web API services. + + + + + Adds a named downstream web API service related to a specific configuration section. + + Builder. + Name of the configuration for the service. + This is the name used when calling the service from controller/pages. + Configuration. + The builder for chaining. + + + + Adds a named downstream web API service initialized with delegates. + + Builder. + Name of the configuration for the service. + This is the name which will be used when calling the service from controller/pages. + Action to configure the options. + The builder for chaining. + + + + Extensions for the downstream web API. + + + + + Get a strongly typed response from the web API. + + Output type. + The downstream web API. + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Path to the API endpoint relative to the base URL specified in the configuration. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful in platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + A strongly typed response from the web API. + + + + Calls the web API with an HttpPost, providing strongly typed input and getting + strongly typed output. + + Output type. + Input type. + The downstream web API. + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Path to the API endpoint relative to the base URL specified in the configuration. + Input data sent to the API. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful in platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + A strongly typed response from the web API. + + + + Calls the web API endpoint with an HttpPut, providing strongly typed input data. + + Input type. + The downstream web API. + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Path to the API endpoint relative to the base URL specified in the configuration. + Input data sent to the API. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful in platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + The value returned by the downstream web API. + + + + Calls the web API endpoint with an HttpPut, provinding strongly typed input data + and getting back strongly typed data. + + Output type. + Input type. + The downstream web API. + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Path to the API endpoint relative to the base URL specified in the configuration. + Input data sent to the API. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful in platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + A strongly typed response from the web API. + + + + Call a web API endpoint with an HttpGet, + and return strongly typed data. + + Output type. + The downstream web API. + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful in platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + The value returned by the downstream web API. + + + + Call a web API with a strongly typed input, with an HttpGet. + + Input type. + The downstream web API. + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Input data. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful in platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + The value returned by the downstream web API. + + + + Options passed-in to call downstream web APIs. To call Microsoft Graph, see rather + MicrosoftGraphOptions in the Microsoft.Identity.Web.MicrosoftGraph assembly. + + + + + Base URL for the called downstream web API. For instance "https://graph.microsoft.com/beta/".. + + + + + Path relative to the (for instance "me"). + + + + + HTTP method used to call this downstream web API (by default Get). + + + + + Provides an opportunity to customize the HttpRequestMessage. For example, + to customize the headers. This is called after the message was formed, including + the Authorization header, and just before the message is sent. + + + + + Clone the options (to be able to override them). + + A clone of the options. + + + + Return the downstream web API URL. + + URL of the downstream web API. + + + + Clone the options (to be able to override them). + + A clone of the options. + + + + Interface used to call a downstream web API, for instance from controllers. + + + + + Calls the downstream web API for the user, based on a description of the + downstream web API in the configuration. + + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful on platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + HTTP context in the case where is + , , . + An that the application will process. + + + + Calls the downstream web API for the user, based on a description of the + downstream web API in the configuration. + + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful on platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + HTTP context in the case where is + , , . + An that the application will process. + + + + Calls a downstream web API consuming JSON with some data and returns data. + + Input type. + Output type. + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Input parameter to the downstream web API. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful in platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + The value returned by the downstream web API. + + A list method that returns an IEnumerable<MyItem>>. + + public Task<IEnumerable<MyItem>> GetAsync() + { + return _downstreamWebApi.CallWebApiForUserAsync<object, IEnumerable<MyItem>>( + ServiceName, + null, + options => + { + options.RelativePath = $"api/todolist"; + }); + } + + + Example of editing. + + public Task<MyItem> EditAsync(MyItem myItem) + { + return _downstreamWebApi.CallWebApiForUserAsync<MyItem, MyItem>( + ServiceName, + nyItem, + options => + { + options.HttpMethod = HttpMethod.Patch; + options.RelativePath = $"api/todolist/{myItem.Id}"; + }); + } + + + + + + Calls a downstream web API consuming JSON with some data and returns data. + + Input type. + Output type. + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Input parameter to the downstream web API. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful in platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + The value returned by the downstream web API. + + A list method that returns an IEnumerable<MyItem>>. + + public Task<IEnumerable<MyItem>> GetAsync() + { + return _downstreamWebApi.CallWebApiForUserAsync<object, IEnumerable<MyItem>>( + ServiceName, + null, + options => + { + options.RelativePath = $"api/todolist"; + }); + } + + + Example of editing. + + public Task<MyItem> EditAsync(MyItem myItem) + { + return _downstreamWebApi.CallWebApiForUserAsync<MyItem, MyItem>( + ServiceName, + nyItem, + options => + { + options.HttpMethod = HttpMethod.Patch; + options.RelativePath = $"api/todolist/{myItem.Id}"; + }); + } + + + + + + Calls the downstream web API for the app, with the required scopes. + + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Overrides the options proposed in the configuration described + by . + HTTP content in the case where is + , , . + An that the application will process. + + + + Calls the downstream web API for the app, with the required scopes. + + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + Overrides the options proposed in the configuration described + by . + HTTP content in the case where is + , , . + An that the application will process. + + + + Interface to a class that provides the that + adds an authorization header with a token for the application. + + + + + Creates an instance of a that adds + an authorization header with a token for an application. + + + Name of the service describing the downstream web API. Used to + retrieve the appropriate config section. + + + The . + + + + + Creates an instance of a that adds + an authorization header with a token on behalf of the current user. + + + Name of the service describing the downstream web API. Used to + retrieve the appropriate config section. + + + The . + + + + + A DelegatingHandler implementation that add an authorization header with a token for the application. + + + + + Initializes a new instance of the class. + + Token acquisition service. + Named options provider. + Name of the service describing the downstream web API. + + + + + + + Base class for Microsoft Identity authentication message handlers. + + + + + Gets the token acquisition service. + + + + + Initializes a new instance of the class. + + Token acquisition service. + Named options provider. + Name of the service describing the downstream web API. + + + + Gets the options for the specified request. + + The request. + The configured options. + + + + Base options passed-in to authenticate with Microsoft Identity. + + + + + Space separated scopes required to call the downstream web API. + For instance "user.read mail.read". + + + + + [Optional] tenant ID. This is used for specific scenarios where + the application needs to call a downstream web API on behalf of a user in several tenants. + It would mostly be used from code, not from the configuration. + + + + + [Optional]. User flow (in the case of a B2C downstream web API). If not + specified, the B2C downstream web API will be called with the default user flow from + . + + + + + Modifies the token acquisition request so that the acquired token is a Proof of Possession token (PoP), + rather than a Bearer token. + PoP tokens are similar to Bearer tokens, but are bound to the HTTP request and to a cryptographic key, + which MSAL can manage. See https://aka.ms/msal-net-pop. + Set to true to enable PoP tokens automatically. + + + + + Options passed-in to create the token acquisition object which calls into MSAL .NET. + + + + + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + + + + + Returns the scopes. + + Scopes. + + + + Extension for IHttpClientBuilder for startup initialization of Microsoft Identity authentication handlers. + + + + + Adds a named Microsoft Identity user authentication message handler related to a specific configuration section. + + Builder. + Name of the configuration for the service. + Configuration. + The builder for chaining. + + + + Adds a named Microsoft Identity user authentication message handler initialized with delegates. + + Builder. + Name of the configuration for the service. + Action to configure the options. + The builder for chaining. + + + + Adds a named Microsoft Identity application authentication message handler related to a specific configuration section. + + Builder. + Name of the configuration for the service. + Configuration. + The builder for chaining. + + + + Adds a named Microsoft Identity application authentication message handler initialized with delegates. + + Builder. + Name of the configuration for the service. + Action to configure the options. + The builder for chaining. + + + + Adds the common configuration for message handlers. + + + The to configure. + + + A Func that takes the and returns + the . This func allows us to reuse the logic to add message handlers, + while allowing the caller to decide if it needs an app handler or a user handler. + + + + + Options passed-in to Microsoft Identity message handlers. + + + + + Clone the options (to be able to override them). + + A clone of the options. + + + + Clone the options (to be able to override them). + + A clone of the options. + + + + A DelegatingHandler implementation that add an authorization header with a token on behalf of the current user. + + + + + Initializes a new instance of the class. + + Token acquisition service. + Named options provider. + Configuration options. + Name of the service describing the downstream web API. + + + + + + + Extension methods. + + + + Determines whether the specified string collection contains any. + The search for. + The string collection. + + true if the specified string collection contains any; otherwise, false. + + + + Keep the validated token associated with the HTTP request. + + HTTP context. + Token to preserve after the token is validated so that + it can be used in the actions. + + + + Get the parsed information about the token used to call the web API. + + HTTP context associated with the current request. + used to call the web API. + + + + Provides access to get or set the current error status. + The default implementation will use TempData and be enabled when run under Development. + + + + + Gets the error message for the current request. + + Current . + The current error message if available. + + + + Sets the error message for the current request. + + Current . + Error message to set. + + + + Gets whether error messages should be displayed. + + + + + Helper methods to handle incremental consent and conditional access in + a web app. + + + + + Can the exception be solved by re-signing-in the user?. + + Exception from which the decision will be made. + Returns true if the issue can be solved by signing-in + the user, and false, otherwise. + + + + Build authentication properties needed for incremental consent. + + Scopes to request. + instance. + User. + Userflow being invoked for AAD B2C. + AuthenticationProperties. + + + + An implementation of IConfigurationRetriever geared towards Azure AD issuers metadata. + + + + Retrieves a populated configuration given an address and an . + Address of the discovery document. + The to use to read the discovery document. + A cancellation token that can be used by other objects or threads to receive notice of cancellation. . + + A that, when completed, returns from the configuration. + + address - Azure AD Issuer metadata address URL is required + or retriever - No metadata document retriever is provided. + + + + Model class to hold information parsed from the Azure AD issuer endpoint. + + + + + Issuer associated with the OIDC endpoint. + + + + + Interface for the token acquisition service (encapsulating MSAL.NET). + + + + + Typically used from an ASP.NET Core web app or web API controller. This method gets an access token + for a downstream API on behalf of the user account for which the claims are provided in the + member of the controller's parameter. + + Scopes to request for the downstream API to call. + Enables to override the tenant/account for the same identity. This is useful in the + cases where a given account is guest in other tenants, and you want to acquire tokens for a specific tenant. + Azure AD B2C UserFlow to target. + Optional claims principal representing the user. If not provided, will use the signed-in + user (in a web app), or the user for which the token was received (in a web API) + cases where a given account is guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest in. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An access token to call on behalf of the user, the downstream API characterized by its scopes. + + + + Typically used from an ASP.NET Core web app or web API controller. This method gets an access token + for a downstream API on behalf of the user account for which the claims are provided in the + member of the controller's parameter. + + Scopes to request for the downstream API to call. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + Enables to override the tenant/account for the same identity. This is useful in the + cases where a given account is guest in other tenants, and you want to acquire tokens for a specific tenant. + Azure AD B2C UserFlow to target. + Optional claims principal representing the user. If not provided, will use the signed-in + user (in a web app), or the user for which the token was received (in a web API) + cases where a given account is guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest in. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An access token to call on behalf of the user, the downstream API characterized by its scopes. + + + + Typically used from an ASP.NET Core web app or web API controller. This method gets an access token + for a downstream API on behalf of the user account for which the claims are provided in the + member of the controller's parameter. + + Scopes to request for the downstream API to call. + Enables to override the tenant/account for the same identity. This is useful in the + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. + Azure AD B2C UserFlow to target. + Optional claims principal representing the user. If not provided, will use the signed-in + user (in a web app), or the user for which the token was received (in a web API) + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest in. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An to call on behalf of the user, the downstream API characterized by its scopes. + + + + Typically used from an ASP.NET Core web app or web API controller. This method gets an access token + for a downstream API on behalf of the user account for which the claims are provided in the + member of the controller's parameter. + + Scopes to request for the downstream API to call. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web APIs. + Enables to override the tenant/account for the same identity. This is useful in the + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. + Azure AD B2C UserFlow to target. + Optional claims principal representing the user. If not provided, will use the signed-in + user (in a web app), or the user for which the token was received (in a web API) + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest in. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An to call on behalf of the user, the downstream API characterized by its scopes. + + + + Acquires a token from the authority configured in the app, for the confidential client itself (not on behalf of a user) + using the client credentials flow. See https://aka.ms/msal-net-client-credentials. + + The scope requested to access a protected API. For this flow (client credentials), the scope + should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft + Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration + in the portal, cannot be overridden in the application, as you can request a token for only one resource at a time (use + several calls to get tokens for other resources). + Enables overriding of the tenant/account for the same identity. This is useful in the + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An access token for the app itself, based on its scopes. + + + + Acquires a token from the authority configured in the app, for the confidential client itself (not on behalf of a user) + using the client credentials flow. See https://aka.ms/msal-net-client-credentials. + + The scope requested to access a protected API. For this flow (client credentials), the scope + should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft + Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration + in the portal, cannot be overridden in the application, as you can request a token for only one resource at a time (use + several calls to get tokens for other resources). + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + Enables overriding of the tenant/account for the same identity. This is useful in the + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An access token for the app itself, based on its scopes. + + + + Acquires an authentication result from the authority configured in the app, for the confidential client itself (not on behalf of a user) + using the client credentials flow. See https://aka.ms/msal-net-client-credentials. + + The scope requested to access a protected API. For this flow (client credentials), the scope + should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft + Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration + in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use + several calls to get tokens for other resources). + Enables overriding of the tenant/account for the same identity. This is useful + for multi tenant apps or daemons. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An authentication result for the app itself, based on its scopes. + + + + Acquires an authentication result from the authority configured in the app, for the confidential client itself (not on behalf of a user) + using the client credentials flow. See https://aka.ms/msal-net-client-credentials. + + The scope requested to access a protected API. For this flow (client credentials), the scope + should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft + Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration + in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use + several calls to get tokens for other resources). + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + Enables overriding of the tenant/account for the same identity. This is useful + for multi tenant apps or daemons. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An authentication result for the app itself, based on its scopes. + + + + Used in web APIs (which therefore cannot have an interaction with the user). + Replies to the client through the HttpResponse by sending a 403 (forbidden) and populating wwwAuthenticateHeaders so that + the client can trigger an interaction with the user so the user can consent to more scopes. + + Scopes to consent to. + triggering the challenge. + The to update. + + + + Used in web APIs (which therefore cannot have an interaction with the user). + Replies to the client through the HttpResponse by sending a 403 (forbidden) and populating wwwAuthenticateHeaders so that + the client can trigger an interaction with the user so the user can consent to more scopes. + + Scopes to consent to. + triggering the challenge. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + The to update. + + + + Get the effective authentication scheme based on the context. + + Proposed authentication scheme. + Effective authenticationScheme which is the authentication scheme + if it's not null, or otherwise OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + + + + Used in web APIs (which therefore cannot have an interaction with the user). + Replies to the client through the HttpResponse by sending a 403 (forbidden) and populating wwwAuthenticateHeaders so that + the client can trigger an interaction with the user so the user can consent to more scopes. + + Scopes to consent to. + triggering the challenge. + The to update. + A representing the asynchronous operation. + + + + Interface for the internal operations of token acquisition service (encapsulating MSAL.NET). + + + + + In a web app, adds, to the MSAL.NET cache, the account of the user authenticating to the web app, when the authorization code is received (after the user + signed-in and consented) + An On-behalf-of token contained in the is added to the cache, so that it can then be used to acquire another token on-behalf-of the + same user in order to call to downstream APIs. + + The context used when an 'AuthorizationCode' is received over the OpenIdConnect protocol. + Scopes to request. + Authentication scheme to use. + A that represents a completed add to cache operation. + + From the configuration of the Authentication of the ASP.NET Core web API: + OpenIdConnectOptions options; + + Subscribe to the authorization code received event: + + options.Events = new OpenIdConnectEvents(); + options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived; + } + + + And then in the OnAuthorizationCodeRecieved method, call : + + private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context) + { + var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); + await _tokenAcquisition.AddAccountToCacheFromAuthorizationCode(context, new string[] { "user.read" }); + } + + + + + + Removes the account associated with context.HttpContext.User from the MSAL.NET cache. + + RedirectContext passed-in to a + OpenID Connect event. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web APIs. + A that represents a completed remove from cache operation. + + + + EventIds for Logging. + + + + + Options for configuring authentication using Azure Active Directory. It has both AAD and B2C configuration attributes. + Merges the MicrosoftIdentityWebOptions and the ConfidentialClientApplicationOptions. + + + + + Base class for web app and web API Microsoft Identity authentication + builders. + + + + + Constructor. + + The services being configured. + Optional configuration section. + + + + The services being configured. + + + + + Configuration section from which to bind options. + + It can be null if the configuration happens with delegates + rather than configuration. + + + + Extensions for IServerSideBlazorBuilder for startup initialization of web APIs. + + + + + Add the incremental consent and conditional access handler for Blazor + server side pages. + + Service side blazor builder. + The builder. + + + + Add the incremental consent and conditional access handler for + web app pages, Razor pages, controllers, views, etc... + + Service collection. + The service collection. + + + + Handler for Blazor specific APIs to handle incremental consent + and conditional access. + + + + + Initializes a new instance of the class. + + Service provider to get the HttpContextAccessor for the current HttpContext, when available. + + + + Boolean to determine if server is Blazor. + + + + + Current user. + + + + + Base URI to use in forming the redirect. + + + + + For Blazor/Razor pages to process the exception from + a user challenge. + + Exception. + + + + Forces the user to consent to specific scopes and perform + Conditional Access to get specific claims. Use on a Razor/Blazor + page or controller to proactively ensure the scopes and/or claims + before acquiring a token. The other mechanism + ensures claims and scopes requested by Azure AD after a failed token acquisition attempt. + See https://aka.ms/ms-id-web/ca_incremental-consent for details. + + Scopes to request. + Claims to ensure. + Userflow being invoked for AAD B2C. + + + + Options for configuring authentication using Azure Active Directory. It has both AAD and B2C configuration attributes. + + + + + Gets or sets the Azure Active Directory instance, e.g. "https://login.microsoftonline.com". + + + + + Gets or sets the tenant ID. + + + + + Gets or sets the domain of the Azure Active Directory tenant, e.g. contoso.onmicrosoft.com. + + + + + Gets or sets the edit profile user flow name for B2C, e.g. b2c_1_edit_profile. + + + + + Gets or sets the sign up or sign in user flow name for B2C, e.g. b2c_1_susi. + + + + + Gets or sets the reset password user flow name for B2C, e.g. B2C_1_password_reset. + + + + + Gets the default user flow (which is signUpsignIn). + + + + + Enables legacy ADAL cache serialization and deserialization. + Performance improvements when working with MSAL only apps. + Set to true if you have a shared cache with ADAL apps. + + The default is false. + + + + Is considered B2C if the attribute SignUpSignInPolicyId is defined. + + + + + Is considered to have client credentials if the attribute ClientCertificates + or ClientSecret is defined. + + + + + Description of the certificates used to prove the identity of the web app or web API. + + An example in the appsetting.json: + + "ClientCertificates": [ + { + "SourceType": "StoreWithDistinguishedName", + "CertificateStorePath": "CurrentUser/My", + "CertificateDistinguishedName": "CN=WebAppCallingWebApiCert" + } + ] + + See also https://aka.ms/ms-id-web-certificates. + + + + + Description of the certificates used to decrypt an encrypted token in a web API. + + An example in the appsetting.json: + + "TokenDecryptionCertificates": [ + { + "SourceType": "StoreWithDistinguishedName", + "CertificateStorePath": "CurrentUser/My", + "CertificateDistinguishedName": "CN=WebAppCallingWebApiCert" + } + ] + + See also https://aka.ms/ms-id-web-certificates. + + + + + Specifies if the x5c claim (public key of the certificate) should be sent to the STS. + Sending the x5c enables application developers to achieve easy certificate rollover in Azure AD: + this method will send the public certificate to Azure AD along with the token request, + so that Azure AD can use it to validate the subject name based on a trusted issuer policy. + This saves the application admin from the need to explicitly manage the certificate rollover + (either via portal or PowerShell/CLI operation). For details see https://aka.ms/msal-net-sni. + + The default is false. + + + + Requests an auth code for the frontend (SPA using MSAL.js for instance). + See https://aka.ms/msal-net/spa-auth-code for details. + + The default is false. + + + + Daemon applications can validate a token based on roles, or using the ACL-based authorization + pattern to control tokens without a roles claim. If using ACL-based authorization, + Microsoft Identity Web will not throw if roles or scopes are not in the Claims. + For details see https://aka.ms/ms-identity-web/daemon-ACL. + + The default is false. + + + + Used, when deployed to Azure, to specify explicitly a user assigned managed identity. + See https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-manage-ua-identity-portal. + + + + + Options for configuring certificateless + + + + + Sets the ResetPassword route path. + Defaults to /MicrosoftIdentity/Account/ResetPassword, + which is the value used by Microsoft.Identity.Web.UI. + + + + + Sets the Error route path. + Defaults to the value /MicrosoftIdentity/Account/Error, + which is the value used by Microsoft.Identity.Web.UI. + + + + + Microsoft Identity Web specific exception class for + use in Blazor or Razor pages to process the user challenge. + Handles the . + + + + + Exception thrown by MSAL when a user challenge is encountered. + + + + + Scopes to request. + + + + + Specified userflow. + + + + + Handles the user challenge for Blazor or Razor pages. + + Exception thrown by MSAL when a user challenge is encountered. + Scopes to request. + Userflow used in B2C. + + + + This is the metadata that describes required auth scopes for a given endpoint + in a web API. It's the underlying data structure the requirement will look for + in order to validate scopes in the scope claims. + + + + + Scopes accepted by this web API. + + + + + Fully qualified name of the configuration key containing the required scopes (separated + by spaces). + + + + + This is the metadata that describes required auth scopes or app permissions for a given endpoint + in a web API. It's the underlying data structure the requirement will look for + in order to validate scopes in the scope claims or app permissions in the roles claim. + + + + + App permissions accepted by this web API. + App permissions appear in the roles claim of the token. + + + + + Fully qualified name of the configuration key containing the required + app permissions (separated by spaces). + + + + + Scopes accepted by this web API. + + + + + Fully qualified name of the configuration key containing the required scopes (separated + by spaces). + + + + + Extensions for building the RequiredScope policy during application startup. + + + + services.AddAuthorization(o => + { o.AddPolicy("Custom", + policyBuilder =>policyBuilder.RequireScope("access_as_user")); + }); + + + + + + Adds a to the current instance which requires + that the current user has the specified claim and that the claim value must be one of the allowed values. + + Used for building policies during application startup. + Values the claim must process one or more of for evaluation to succeed. + A reference to this instance after the operation has completed. + + + + Adds a to the current instance which requires + that the current user has the specified claim and that the claim value must be one of the allowed values. + + Used for building policies during application startup. + Values the claim must process one or more of for evaluation to succeed. + A reference to this instance after the operation has completed. + + + + Adds a to the current instance which requires + that the current user has the specified claim and that the claim value must be one of the allowed values. + + Used for building policies during application startup. + scopes (the value of scope or scp) accepted by this app. + App permission (in role claim) that this app accepts. + A reference to this instance after the operation has completed. + + + + This attribute is used on a controller, pages, or controller actions + to declare (and validate) the scopes required by a web API. These scopes can be declared + in two ways: hardcoding them, or declaring them in the configuration. Depending on your + choice, use either one or the other of the constructors. + For details, see https://aka.ms/ms-id-web/required-scope-attribute. + + + + + Scopes accepted by this web API. + + + + + Fully qualified name of the configuration key containing the required scopes (separated + by spaces). + + + If the appsettings.json file contains a section named "AzureAd", in which + a property named "Scopes" contains the required scopes, the attribute on the + controller/page/action to protect should be set to the following: + + [RequiredScope(RequiredScopesConfigurationKey="AzureAd:Scopes")] + + + + + + Verifies that the web API is called with the right scopes. + If the token obtained for this API is on behalf of the authenticated user does not have + any of these in its scope claim, the + method updates the HTTP response providing a status code 403 (Forbidden) + and writes to the response body a message telling which scopes are expected in the token. + + Scopes accepted by this web API. + When the scopes don't match, the response is a 403 (Forbidden), + because the user is authenticated (hence not 401), but not authorized. + + Add the following attribute on the controller/page/action to protect: + + + [RequiredScope("access_as_user")] + + + and + if you want to express the required scopes from the configuration. + + + + Default constructor. + + + + [RequiredScope(RequiredScopesConfigurationKey="AzureAD:Scope")] + class Controller : BaseController + { + } + + + + + + Unused: Compatibility of interface with the Authorization Filter. + + + + + This attribute is used on a controller, pages, or controller actions + to declare (and validate) the scopes or app permissions required by a web API. + These scopes or app permissions can be declared in two ways: + hardcoding them, or declaring them in the configuration. Depending on your + choice, use either one or the other of the constructors. + For details, see https://aka.ms/ms-id-web/required-scope-or-app-permissions-attribute. + + + + + Scopes accepted by this web API. + + + + + Fully qualified name of the configuration key containing the required scopes (separated + by spaces). + + + If the appsettings.json file contains a section named "AzureAd", in which + a property named "Scopes" contains the required scopes, the attribute on the + controller/page/action to protect should be set to the following: + + [RequiredScopeOrAppPermission(RequiredScopesConfigurationKey="AzureAd:Scopes")] + + + + + + App permissions accepted by this web API. + App permissions appear in the roles claim of the token. + + + + + Fully qualified name of the configuration key containing the required app permissions (separated + by spaces). + + + If the appsettings.json file contains a section named "AzureAd", in which + a property named "AppPermissions" contains the required app permissions, the attribute on the + controller/page/action to protect should be set to the following: + + [RequiredScopeOrAppPermission(RequiredAppPermissionsConfigurationKey="AzureAd:AppPermissions")] + + + + + + Verifies that the web API is called with the right app permissions. + If the token obtained for this API is on behalf of the authenticated user does not have + any of these in its scope claim, + nor in its roles claim, the + method updates the HTTP response providing a status code 403 (Forbidden) + and writes to the response body a message telling which scopes are expected in the token. + + Scopes accepted by this web API. + App permissions accepted by this web API. + When neither the scopes nor app permissions match, the response is a 403 (Forbidden), + because the user is authenticated (hence not 401), but not authorized. + + Add the following attribute on the controller/page/action to protect: + + + [RequiredScopeOrAppPermission(new [] { "access_as_user" }, new [] { "access_as_app" })] + + + and + if you want to express the required scopes or app permissions from the configuration. + + + + Default constructor. + + + + [RequiredScopeOrAppPermission(RequiredScopesConfigurationKey="AzureAD:Scope", RequiredAppPermissionsConfigurationKey="AzureAD:AppPermission")] + class Controller : BaseController + { + } + + + + + + Interface implemented by diagnostics for the JWT Bearer middleware. + + + + + Called to subscribe to . + + JWT Bearer events. + The events (for chaining). + + + + Diagnostics used in the OpenID Connect middleware + (used in web apps). + + + + + Method to subscribe to . + + OpenID Connect events. + + + + Diagnostics for the JwtBearer middleware (used in web APIs). + + + + + Constructor for a . This constructor + is used by dependency injection. + + Logger. + + + + Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. + + + + + Invoked when a protocol message is first received. + + + + + Invoked after the security token has passed validation and a ClaimsIdentity has been generated. + + + + + Invoked before a challenge is sent back to the caller. + + + + + Subscribes to all the JwtBearer events, to help debugging, while + preserving the previous handlers (which are called). + + Events to subscribe to. + for chaining. + + + + Factory class for creating the IssuerValidator per authority. + + + + + Initializes a new instance of the class. + + Options passed-in to create the AadIssuerValidator object. + HttpClientFactory. + + + + Gets an for an authority. + + The authority to create the validator for, e.g. https://login.microsoftonline.com/. + A for the aadAuthority. + if is null or empty. + + + + Diagnostics used in the OpenID Connect middleware + (used in web apps). + + + + + Constructor of the , used + by dependency injection. + + Logger used to log the diagnostics. + + + + Invoked before redirecting to the identity provider to authenticate. This can + be used to set ProtocolMessage.State that will be persisted through the authentication + process. The ProtocolMessage can also be used to add or customize parameters + sent to the identity provider. + + + + + Invoked when a protocol message is first received. + + + + + Invoked after security token validation if an authorization code is present + in the protocol message. + + + + + Invoked after "authorization code" is redeemed for tokens at the token endpoint. + + + + + Invoked when an IdToken has been validated and produced an AuthenticationTicket. + + + + + Invoked when user information is retrieved from the UserInfoEndpoint. + + + + + Invoked if exceptions are thrown during request processing. The exceptions will + be re-thrown after this event unless suppressed. + + + + + Invoked when a request is received on the RemoteSignOutPath. + + + + + Invoked before redirecting to the identity provider to sign out. + + + + + Invoked before redirecting to the Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.SignedOutRedirectUri + at the end of a remote sign-out flow. + + + + + Subscribes to all the OpenIdConnect events, to help debugging, while + preserving the previous handlers (which are called). + + Events to subscribe to. + + + + Generic class that registers the token audience from the provided Azure AD authority. + + + + + Default validation of the audience: + - when registering an Azure AD web API in the app registration portal (and adding a scope) + the default App ID URI generated by the portal is api://{clientID} + - However, the audience (aud) of the token acquired to access this web API is different depending + on the "accepted access token version" for the web API: + - if accepted token version is 1.0, the audience provided in the token + by the Microsoft identity platform (formerly Azure AD v2.0) endpoint is: api://{ClientID} + - if the accepted token version is 2.0, the audience provided by Azure AD v2.0 in the token + is {CliendID} + When getting an access token for an Azure AD B2C web API the audience in the token is + api://{ClientID}. + + When web API developers don't provide the "Audience" in the configuration, Microsoft.Identity.Web + considers that this is the default App ID URI as explained above. When developer provides the + "Audience" member, it's available in the TokenValidationParameter.ValidAudience. + + Audiences in the security token. + Security token from which to validate the audiences. + Token validation parameters. + True if the token is valid; false, otherwise. + + + + Extension class providing the extension methods for that + can be used in web APIs to validate the roles in controller actions. + + + + + When applied to an , verifies that the application + has the expected roles. + + HttpContext (from the controller). + Roles accepted by this web API. + When the roles don't match, the response is a 403 (Forbidden), + because the app does not have the expected roles. + + + + Extension class providing the extension + methods for that + can be used in web APIs to validate scopes in controller actions. + We recommend using instead the RequiredScope Attribute on the controller, the page or the action. + See https://aka.ms/ms-id-web/required-scope-attribute. + + + + + When applied to an , verifies that the user authenticated in the + web API has any of the accepted scopes. + If there is no authenticated user, the response is a 401 (Unauthenticated). + If the authenticated user does not have any of these , the + method updates the HTTP response providing a status code 403 (Forbidden) + and writes to the response body a message telling which scopes are expected in the token. + We recommend using instead the RequiredScope Attribute on the controller, the page or the action. + See https://aka.ms/ms-id-web/required-scope-attribute. + + HttpContext (from the controller). + Scopes accepted by this web API. + + + + Extensions for building the required scope attribute during application startup. + + + + + This method adds support for the required scope attribute. It adds a default policy that + adds a scope requirement. This requirement looks for IAuthRequiredScopeMetadata on the current endpoint. + + The services being configured. + Services. + + + + This method adds metadata to route endpoint to describe required scopes. It's the imperative version of + the [RequiredScope] attribute. + + Class implementing . + To customize the endpoints. + Scope. + Builder. + + + + Extensions for building the required scope or app permission attribute during application startup. + + + + + This method adds support for the required scope or app permission attribute. It adds a default policy that + adds a scope requirement or app permission requirement. + This requirement looks for IAuthRequiredScopeOrAppPermissionMetadata on the current endpoint. + + The services being configured. + Services. + + + + This method adds metadata to route endpoint to describe required scopes or app permissions. It's the imperative version of + the [RequiredScopeOrAppPermission] attribute. + + Class implementing . + To customize the endpoints. + Scope. + App permission. + Builder. + + + + RequireScopeOptions. + + + + + Sets the default policy. + + + + + + + + RequireScopeOrAppPermissionOptions. + + + + + Sets the default policy. + + + + + + + + Scope authorization handler that needs to be called for a specific requirement type. + In this case, . + + + + + Constructor for the scope authorization handler, which takes a configuration. + + Configuration. + + + + Makes a decision if authorization is allowed based on a specific requirement. + + AuthorizationHandlerContext. + Scope authorization requirement. + Task. + + + + Implements an + which requires at least one instance of the specified claim type, and, if allowed values are specified, + the claim value must be any of the allowed values. + + + + + Creates a new instance of . + + The optional list of scope values. + + + + Gets the optional list of scope values. + + + + + Gets the optional list of scope values from configuration. + + + + + + + + Scope or app permission authorization handler that needs to be called for a specific requirement type. + In this case, . + + + + + Constructor for the scope authorization handler, which takes a configuration. + + Configuration. + + + + Makes a decision if authorization is allowed based on a specific requirement. + + AuthorizationHandlerContext. + Scope authorization requirement. + Task. + + + + Implements an + which requires at least one instance of the specified claim type, and, if allowed values are specified, + the claim value must be any of the allowed values. + + + + + Creates a new instance of . + + The optional list of scope values. + + + + + Gets the optional list of scope values. + + + + + Gets the optional list of app permission values. + + + + + Gets the optional list of scope values from configuration. + + + + + Gets the optional list of app permission values from configuration. + + + + + + + + Options passed-in to create the AadIssuerValidator object. + + + + + Sets the name of the HttpClient to get from the IHttpClientFactory for use with the configuration manager. + Needed when customizing the client such as configuring a proxy. + + + + + Extensions for IServiceCollection for startup initialization of web APIs. + + + + + Add the token acquisition service. + + Service collection. + Specifies if an instance of should be a singleton. + The service collection. + + This method is typically called from the ConfigureServices(IServiceCollection services) in Startup.cs. + Note that the implementation of the token cache can be chosen separately. + + + // Token acquisition service and its cache implementation as a session cache + services.AddTokenAcquisition() + .AddDistributedMemoryCache() + .AddSession() + .AddSessionBasedTokenCache(); + + + + + + An implementation of that uses to track error messages. + + + + + Token acquisition service. + + + LoggingMessage class for TokenAcquisition. + + + + + Please call GetOrBuildConfidentialClientApplication instead of accessing this field directly. + + + + + Constructor of the TokenAcquisition service. This requires the Azure AD Options to + configure the confidential client application and a token cache provider. + This constructor is called by ASP.NET Core dependency injection. + + The App token cache provider. + Access to the HttpContext of the request. + Configuration options. + HTTP client factory. + Logger. + Service provider. + + + + Scopes which are already requested by MSAL.NET. They should not be re-requested;. + + + + + Meta-tenant identifiers which are not allowed in client credentials. + + + + + This handler is executed after the authorization code is received (once the user signs-in and consents) during the + authorization code flow in a web app. + It uses the code to request an access token from the Microsoft identity platform and caches the tokens and an entry about the signed-in user's account in the MSAL's token cache. + The access token (and refresh token) provided in the , once added to the cache, are then used to acquire more tokens using the + on-behalf-of flow for the signed-in user's account, + in order to call to downstream APIs. + + The context used when an 'AuthorizationCode' is received over the OpenIdConnect protocol. + scopes to request access to. + Authentication scheme to use (by default, OpenIdConnectDefaults.AuthenticationScheme). + + From the configuration of the Authentication of the ASP.NET Core web API: + OpenIdConnectOptions options; + + Subscribe to the authorization code received event: + + options.Events = new OpenIdConnectEvents(); + options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived; + } + + + And then in the OnAuthorizationCodeRecieved method, call : + + private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context) + { + var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); + await _tokenAcquisition.AddAccountToCacheFromAuthorizationCode(context, new string[] { "user.read" }); + } + + + + + + Typically used from a web app or web API controller, this method retrieves an access token + for a downstream API using; + 1) the token cache (for web apps and web APIs) if a token exists in the cache + 2) or the on-behalf-of flow + in web APIs, for the user account that is ascertained from claims provided in the + instance of the current HttpContext. + + Scopes to request for the downstream API to call. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web APIs. + Enables overriding of the tenant/account for the same identity. This is useful in the + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest. + Azure AD B2C user flow to target. + Optional claims principal representing the user. If not provided, will use the signed-in + user (in a web app), or the user for which the token was received (in a web API) + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest. + Options passed-in to create the token acquisition options object which calls into MSAL .NET. + An access token to call the downstream API and populated with this downstream API's scopes. + Calling this method from a web API supposes that you have previously called, + in a method called by JwtBearerOptions.Events.OnTokenValidated, the HttpContextExtensions.StoreTokenUsedToCallWebAPI method + passing the validated token (as a JwtSecurityToken). Calling it from a web app supposes that + you have previously called AddAccountToCacheFromAuthorizationCodeAsync from a method called by + OpenIdConnectOptions.Events.OnAuthorizationCodeReceived. + + + + Acquires an authentication result from the authority configured in the app, for the confidential client itself (not on behalf of a user) + using the client credentials flow. See https://aka.ms/msal-net-client-credentials. + + The scope requested to access a protected API. For this flow (client credentials), the scope + should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft + Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration + in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use + several calls to get tokens for other resources). + AuthenticationScheme to use. + Enables overriding of the tenant/account for the same identity. This is useful + for multi tenant apps or daemons. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An authentication result for the app itself, based on its scopes. + + + + Acquires a token from the authority configured in the app, for the confidential client itself (not on behalf of a user) + using the client credentials flow. See https://aka.ms/msal-net-client-credentials. + + The scope requested to access a protected API. For this flow (client credentials), the scope + should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft + Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration + in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use + several calls to get tokens for other resources). + AuthenticationScheme to use. + Enables overriding of the tenant/account for the same identity. This is useful + for multi tenant apps or daemons. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An access token for the app itself, based on its scopes. + + + + Typically used from a web app or web API controller, this method retrieves an access token + for a downstream API using; + 1) the token cache (for web apps and web APIs) if a token exists in the cache + 2) or the on-behalf-of flow + in web APIs, for the user account that is ascertained from the claims provided in the + instance of the current HttpContext. + + Scopes to request for the downstream API to call. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + Enables overriding of the tenant/account for the same identity. This is useful in the + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. + Azure AD B2C user flow to target. + Optional claims principal representing the user. If not provided, will use the signed-in + user (in a web app), or the user for which the token was received (in a web API) + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An access token to call the downstream API and populated with this downstream API's scopes. + Calling this method from a web API supposes that you have previously called, + in a method called by JwtBearerOptions.Events.OnTokenValidated, the HttpContextExtensions.StoreTokenUsedToCallWebAPI method + passing the validated token (as a JwtSecurityToken). Calling it from a web app supposes that + you have previously called AddAccountToCacheFromAuthorizationCodeAsync from a method called by + OpenIdConnectOptions.Events.OnAuthorizationCodeReceived. + + + + Used in web APIs (no user interaction). + Replies to the client through the HTTP response by sending a 403 (forbidden) and populating the 'WWW-Authenticate' header so that + the client, in turn, can trigger a user interaction so that the user consents to more scopes. + + Scopes to consent to. + The that triggered the challenge. + The to update. + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + + + + Used in web APIs (no user interaction). + Replies to the client through the HTTP response by sending a 403 (forbidden) and populating the 'WWW-Authenticate' header so that + the client, in turn, can trigger a user interaction so that the user consents to more scopes. + + Scopes to consent to. + The that triggered the challenge. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + The to update. + + + + Removes the account associated with context.HttpContext.User from the MSAL.NET cache. + + RedirectContext passed-in to a + OpenID Connect event. + Authentication scheme. If null, will use OpenIdConnectDefault.AuthenticationScheme + if called from a web app, and JwtBearerDefault.AuthenticationScheme if called from a web API. + A that represents a completed account removal operation. + + + + + + + Creates an MSAL confidential client application. + + + + + Gets an access token for a downstream API on behalf of the user described by its claimsPrincipal. + + . + Claims principal for the user on behalf of whom to get a token. + Scopes for the downstream API to call. + (optional) TenantID based on a specific tenant for which to acquire a token to access the scopes + on behalf of the user described in the claimsPrincipal. + Merged options. + Azure AD B2C user flow to target. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + + + + Gets an access token for a downstream API on behalf of the user whose account is passed as an argument. + + . + User IAccount for which to acquire a token. + See . + Scopes for the downstream API to call. + TenantID based on a specific tenant for which to acquire a token to access the scopes + on behalf of the user. + Merged options. + Azure AD B2C user flow. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + + + + Logger for handling MSAL exceptions in TokenAcquisition. + + ILogger. + Specific log message from TokenAcquisition. + Exception from MSAL.NET. + + + + Logger for handling information specific to MSAL in token acquisition. + + ILogger. + durationTotalInMs. + durationInHttpInMs. + durationInCacheInMs. + cache or IDP. + correlationId. + cacheRefreshReason. + Exception from MSAL.NET. + + + + Options passed-in to create the token acquisition object which calls into MSAL .NET. + + + + + Sets the correlation id to be used in the authentication request + to the /token endpoint. + + + + + Sets Extra Query Parameters for the query string in the HTTP authentication request. + + + + + A string with one or multiple claims to request. + Normally used with Conditional Access. + + + + + Specifies if the token request will ignore the access token in the token cache + and will attempt to acquire a new access token. + If true, the request will ignore the token cache. The default is false. + Use this option with care and only when needed, for instance, if you know that conditional access policies have changed, + for it induces performance degradation, as the token cache is not utilized. + + + + + Modifies the token acquisition request so that the acquired token is a Proof of Possession token (PoP), + rather than a Bearer token. + PoP tokens are similar to Bearer tokens, but are bound to the HTTP request and to a cryptographic key, + which MSAL can manage. See https://aka.ms/msal-net-pop. + + + + + Cancellation token to be used when calling the token acquisition methods. + + + + + Key used for long running web APIs that need to call downstream web + APIs on behalf of the user. Can be null, if you are not developing a long + running web API, if you want + Microsoft.Identity.Web to allocate a session key for you, or your own string + if you want to associate the session with some information you have externally + (for instance a Microsoft Graph hook identifier). + + + + + Value that can be used for so that + MSAL.NET allocates the long running web api session key for the developer. + + + + + Clone the options (to be able to override them). + + A clone of the options. + + + + An implementation of token cache for confidential clients backed by an HTTP session. + + + For this session cache to work effectively, the ASP.NET Core session has to be configured properly. + The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state + + In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: + + services.AddSession(option => + { + option.Cookie.IsEssential = true; + }); + + In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: + + app.UseSession(); // Before UseMvc() + + + https://aka.ms/msal-net-token-cache-serialization + + LoggingMessage class for MsalSessionTokenCacheProvider. + + + + + MSAL Token cache provider constructor. + + Session for the current user. + Logger. + + + + Read a blob representing the token cache from its key. + + Key representing the token cache + (account or app). + Read blob. + + + + Read a blob representing the token cache from its key. + + Key representing the token cache + (account or app). + Hints for the cache serialization implementation optimization. + Read blob. + + + + Writes the token cache identified by its key to the serialization mechanism. + + Key for the cache (account ID or app ID). + Blob to write to the cache. + A that completes when a write operation has completed. + + + + Removes a cache described by its key. + + Key of the token cache (user account or app ID). + A that completes when key removal has completed. + + + + LoggingMessage class for MsalSessionTokenCacheProvider. + + + + + Session cache logging. + + ILogger. + /// Cache operation (Read, Write, etc...). + Session Id. + MSAL.NET cache key. + Exception. + + + + Session cache deserialized. + + ILogger. + MSAL.NET cache key. + Session Id. + Exception. + + + + Extension class to add a session token cache serializer to MSAL. + + + + + Adds an HTTP session-based application token cache to the service collection. + + + For this session cache to work effectively the ASP.NET Core session has to be configured properly. + The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state. + + In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: + + services.AddSession(option => + { + option.Cookie.IsEssential = true; + }); + + In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: + + app.UseSession(); // Before UseMvc() + + Because session token caches are added with scoped lifetime, they should not be used when TokenAcquisition is also used as a singleton (for example, when using Microsoft Graph SDK). + + The services collection to add to. + The service collection. + + + + Adds an HTTP session-based per-user token cache to the service collection. + + + For this session cache to work effectively the ASP.NET Core session has to be configured properly. + The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state. + + In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: + + services.AddSession(option => + { + option.Cookie.IsEssential = true; + }); + + In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: + + app.UseSession(); // Before UseMvc() + + Because session token caches are added with scoped lifetime, they should not be used when TokenAcquisition is also used as a singleton (for example, when using Microsoft Graph SDK). + + The services collection to add to. + The service collection. + + + + Encoding table. + + + + + The following functions perform base64url encoding which differs from regular base64 encoding as follows + * padding is skipped so the pad character '=' doesn't have to be percent encoded + * the 62nd and 63rd regular base64 encoding characters ('+' and '/') are replace with ('-' and '_') + The changes make the encoding alphabet file and URL safe. + + string to encode. + Base64Url encoding of the UTF8 bytes. + + + + Converts a subset of an array of 8-bit unsigned integers to its equivalent string representation that is encoded with base-64-url digits. Parameters specify + the subset as an offset in the input array, and the number of elements in the array to convert. + + An array of 8-bit unsigned integers. + The number of elements of inArray to convert. + An offset in inArray. + The string representation in base 64 url encoding of length elements of inArray, starting at position offset. + 'inArray' is null. + offset or length is negative OR offset plus length is greater than the length of inArray. + + + + Converts a subset of an array of 8-bit unsigned integers to its equivalent string representation that is encoded with base-64-url digits. Parameters specify + the subset as an offset in the input array, and the number of elements in the array to convert. + + An array of 8-bit unsigned integers. + The string representation in base 64 url encoding of length elements of inArray, starting at position offset. + 'inArray' is null. + offset or length is negative OR offset plus length is greater than the length of inArray. + + + + Converts the specified string, which encodes binary data as base-64-url digits, to an equivalent 8-bit unsigned integer array. + base64Url encoded string. + UTF8 bytes. + + + + Decodes the string from Base64UrlEncoded to UTF8. + + string to decode. + UTF8 string. + + + + Authentication builder for a web API. + + + + + Constructor. + + The services being configured. + Default scheme used for OpenIdConnect. + ACtion called to configure the JwtBearer options. + Action called to configure + the Microsoft identity options. + Configuration section from which to + get parameters. + + + + Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). + + The action to configure . + The authentication builder to chain. + + + + Extensions for for startup initialization of web APIs. + + + + + Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). + This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. + + The to which to add this configuration. + The configuration instance. + The configuration section with the necessary settings to initialize authentication options. + The JWT bearer scheme name to be used. By default it uses "Bearer". + + Set to true if you want to debug, or just understand the JWT bearer events. + + The authentication builder to chain. + + + + Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). + This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. + + The to which to add this configuration. + The configuration second from which to fill-in the options. + The JWT bearer scheme name to be used. By default it uses "Bearer". + + Set to true if you want to debug, or just understand the JWT bearer events. + + The authentication builder to chain. + + + + Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). + + The to which to add this configuration. + The action to configure . + The action to configure the . + The JWT bearer scheme name to be used. By default it uses "Bearer". + + Set to true if you want to debug, or just understand the JWT bearer events. + The authentication builder to chain. + + + + In order to ensure that the Web API only accepts tokens from tenants where it has been consented and provisioned, a token that + has neither Roles nor Scopes claims should be rejected. To enforce that rule, add an event handler to the beginning of the + handler chain that rejects tokens that don't meet the rules. + + The object to modify. + The JWT bearer scheme name to be used. By default it uses "Bearer". + + + + Builder for web API authentication with configuration. + + + + + Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). + This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. + + The authentication builder to chain. + + + + Extension for IServiceCollection for startup initialization of web APIs. + + + + + Protects the web API with Microsoft identity platform (formerly Azure AD v2.0) + This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. + + Service collection to which to add authentication. + The Configuration object. + The configuration section with the necessary settings to initialize authentication options. + The JwtBearer scheme name to be used. By default it uses "Bearer". + + Set to true if you want to debug, or just understand the JwtBearer events. + The authentication builder to chain extension methods. + + + + Web API authentication builder. + + + + + Allows a higher level abstraction of security token (i.e. System.IdentityModel.Tokens.Jwt and more modern, Microsoft.IdentityModel.JsonWebTokens) + to be used with Microsoft Identity Web. + Developers should continue to use `EnableTokenAcquisitionToCallDownstreamApi`. + This API is not considered part of the public API and may change. + + The action to configure . + Authentication scheme. + The services being configured. + IConfigurationSection. + The authentication builder to chain. + + + + Authentication builder returned by the EnableTokenAcquisitionToCallDownstreamApi methods + enabling you to decide token cache implementations. + + + + + Add in memory token caches. + + to configure. + to configure. + the service collection. + + + + Add distributed token caches. + + the service collection. + + + + Add session token caches. + + the service collection. + + + + Authentication builder specific for Microsoft identity platform. + + + + + Constructor. + + The services being configured. + Default scheme used for OpenIdConnect. + Action called to configure + the Microsoft identity options. + Optional configuration section. + + + + The web app calls a web API. + + Initial scopes. + The builder itself for chaining. + + + + The web app calls a web API. This override enables you to specify the + ConfidentialClientApplicationOptions (from MSAL.NET) programmatically. + + Action to configure the + MSAL.NET confidential client application options. + Initial scopes. + The builder itself for chaining. + + + + Extensions for the for startup initialization. + + + + + Add authentication to a web app with Microsoft identity platform. + This method expects the configuration file will have a section, named "AzureAd" as default, + with the necessary settings to initialize authentication options. + + The to which to add this configuration. + The configuration instance. + The configuration section with the necessary settings to initialize authentication options. + The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". + The cookie-based scheme name to be used. By default it uses "Cookies". + Set to true if you want to debug, or just understand the OpenID Connect events. + A display name for the authentication handler. + The builder for chaining. + + + + Add authentication with Microsoft identity platform. + This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. + + The to which to add this configuration. + The configuration section from which to get the options. + The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". + The cookie-based scheme name to be used. By default it uses "Cookies". + Set to true if you want to debug, or just understand the OpenID Connect events. + A display name for the authentication handler. + The authentication builder for chaining. + + + + Add authentication with Microsoft identity platform. + + The to which to add this configuration. + The action to configure . + The action to configure . + The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". + The cookie-based scheme name to be used. By default it uses "Cookies". + Set to true if you want to debug, or just understand the OpenID Connect events. + A display name for the authentication handler. + The authentication builder for chaining. + + + + Add authentication with Microsoft identity platform. + + The to which to add this configuration. + The action to configure . + The action to configure . + The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". + The cookie-based scheme name to be used. By default it uses "Cookies". + Set to true if you want to debug, or just understand the OpenID Connect events. + A display name for the authentication handler. + Configuration section. + The authentication builder for chaining. + + + + Add authentication with Microsoft identity platform. + + The to which to add this configuration. + The action to configure . + The action to configure . + The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". + The cookie-based scheme name to be used. By default it uses "Cookies". + Set to true if you want to debug, or just understand the OpenID Connect events. + A display name for the authentication handler. + The authentication builder for chaining. + + + + Builder for a Microsoft identity web app authentication where configuration is + available for EnableTokenAcquisitionToCallDownstreamApi. + + + + + Constructor. + + The services being configured. + Default scheme used for OpenIdConnect. + Action called to configure + the Microsoft identity options. + Optional configuration section. + + + + Add support for the web app to acquire tokens to call an API. + + Optional initial scopes to request. + The authentication builder for chaining. + + + + Extension for IServiceCollection for startup initialization. + + + + + Add authentication with Microsoft identity platform. + This method expects the configuration file will have a section, (by default named "AzureAd"), with the necessary settings to + initialize the authentication options. + + Service collection to which to add authentication. + The IConfiguration object. + The name of the configuration section with the necessary + settings to initialize authentication options. + Optional name for the open id connect authentication scheme + (by default OpenIdConnectDefaults.AuthenticationScheme). This can be specified when you want to support + several OpenIdConnect identity providers. + Optional name for the cookie authentication scheme + (by default CookieAuthenticationDefaults.AuthenticationScheme). + Set to true if you want to debug, or just understand the OpenIdConnect events. + A display name for the authentication handler. + The authentication builder to chain extension methods. + diff --git a/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeMetadata.cs b/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeMetadata.cs index 25a3ee3c0..6d34324bc 100644 --- a/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeMetadata.cs +++ b/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeMetadata.cs @@ -15,7 +15,7 @@ public interface IAuthRequiredScopeMetadata /// /// Scopes accepted by this web API. /// - IEnumerable? AcceptedScope { get; } + string[]? AcceptedScope { get; } /// /// Fully qualified name of the configuration key containing the required scopes (separated diff --git a/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs b/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs new file mode 100644 index 000000000..9e7597431 --- /dev/null +++ b/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Identity.Web +{ + /// + /// This is the metadata that describes required auth scopes or app permissions for a given endpoint + /// in a web API. It's the underlying data structure the requirement will look for + /// in order to validate scopes in the scope claims or app permissions in the roles claim. + /// + public interface IAuthRequiredScopeOrAppPermissionMetadata + { + /// + /// App permissions accepted by this web API. + /// App permissions appear in the roles claim of the token. + /// + string[]? AcceptedAppPermission { get; } + + /// + /// Fully qualified name of the configuration key containing the required + /// app permissions (separated by spaces). + /// + string? RequiredAppPermissionsConfigurationKey { get; } + + /// + /// Scopes accepted by this web API. + /// + string[]? AcceptedScope { get; } + + /// + /// Fully qualified name of the configuration key containing the required scopes (separated + /// by spaces). + /// + string? RequiredScopesConfigurationKey { get; } + } +} diff --git a/src/Microsoft.Identity.Web/Policy/PolicyBuilderExtensions.cs b/src/Microsoft.Identity.Web/Policy/PolicyBuilderExtensions.cs index d5c5ac2ae..3e5f63a16 100644 --- a/src/Microsoft.Identity.Web/Policy/PolicyBuilderExtensions.cs +++ b/src/Microsoft.Identity.Web/Policy/PolicyBuilderExtensions.cs @@ -58,5 +58,29 @@ public static AuthorizationPolicyBuilder RequireScope( authorizationPolicyBuilder.Requirements.Add(new ScopeAuthorizationRequirement(allowedValues)); return authorizationPolicyBuilder; } + + /// + /// Adds a to the current instance which requires + /// that the current user has the specified claim and that the claim value must be one of the allowed values. + /// + /// Used for building policies during application startup. + /// scopes (the value of scope or scp) accepted by this app. + /// App permission (in role claim) that this app accepts. + /// A reference to this instance after the operation has completed. + public static AuthorizationPolicyBuilder RequireScopeOrAppPermission( + this AuthorizationPolicyBuilder authorizationPolicyBuilder, + IEnumerable allowedScopeValues, + IEnumerable allowedAppPermissionValues) + { + if (authorizationPolicyBuilder == null) + { + throw new ArgumentNullException(nameof(authorizationPolicyBuilder)); + } + + authorizationPolicyBuilder.Requirements.Add(new ScopeOrAppPermissionAuthorizationRequirement( + allowedScopeValues, + allowedAppPermissionValues)); + return authorizationPolicyBuilder; + } } } diff --git a/src/Microsoft.Identity.Web/Policy/RequireScopeOrAppPermissionOptions.cs b/src/Microsoft.Identity.Web/Policy/RequireScopeOrAppPermissionOptions.cs new file mode 100644 index 000000000..787951d26 --- /dev/null +++ b/src/Microsoft.Identity.Web/Policy/RequireScopeOrAppPermissionOptions.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Options; + +namespace Microsoft.Identity.Web +{ + /// + /// RequireScopeOrAppPermissionOptions. + /// + internal class RequireScopeOrAppPermissionOptions : IPostConfigureOptions + { + private readonly AuthorizationPolicy _defaultPolicy; + + /// + /// Sets the default policy. + /// + public RequireScopeOrAppPermissionOptions() + { + _defaultPolicy = new AuthorizationPolicyBuilder() + .AddRequirements(new ScopeOrAppPermissionAuthorizationRequirement()) + .Build(); + } + + /// + public void PostConfigure( + string name, + AuthorizationOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.DefaultPolicy = options.DefaultPolicy is null + ? _defaultPolicy + : AuthorizationPolicy.Combine(options.DefaultPolicy, _defaultPolicy); + } + } +} diff --git a/src/Microsoft.Identity.Web/Policy/RequiredScopeAttribute.cs b/src/Microsoft.Identity.Web/Policy/RequiredScopeAttribute.cs index 0dc314361..32a731965 100644 --- a/src/Microsoft.Identity.Web/Policy/RequiredScopeAttribute.cs +++ b/src/Microsoft.Identity.Web/Policy/RequiredScopeAttribute.cs @@ -19,7 +19,7 @@ public class RequiredScopeAttribute : Attribute, IAuthRequiredScopeMetadata /// /// Scopes accepted by this web API. /// - public IEnumerable? AcceptedScope { get; set; } + public string[]? AcceptedScope { get; set; } /// /// Fully qualified name of the configuration key containing the required scopes (separated diff --git a/src/Microsoft.Identity.Web/Policy/RequiredScopeExtensions.cs b/src/Microsoft.Identity.Web/Policy/RequiredScopeExtensions.cs index 39cc41c04..304682f18 100644 --- a/src/Microsoft.Identity.Web/Policy/RequiredScopeExtensions.cs +++ b/src/Microsoft.Identity.Web/Policy/RequiredScopeExtensions.cs @@ -52,7 +52,7 @@ public RequiredScopeMetadata(string[] scope) AcceptedScope = scope; } - public IEnumerable? AcceptedScope { get; } + public string[]? AcceptedScope { get; } public string? RequiredScopesConfigurationKey { get; } } diff --git a/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionAttribute.cs b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionAttribute.cs new file mode 100644 index 000000000..384581e81 --- /dev/null +++ b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionAttribute.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Identity.Web.Resource +{ + /// + /// This attribute is used on a controller, pages, or controller actions + /// to declare (and validate) the scopes or app permissions required by a web API. + /// These scopes or app permissions can be declared in two ways: + /// hardcoding them, or declaring them in the configuration. Depending on your + /// choice, use either one or the other of the constructors. + /// For details, see https://aka.ms/ms-id-web/required-scope-or-app-permissions-attribute. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class RequiredScopeOrAppPermissionAttribute : Attribute, IAuthRequiredScopeOrAppPermissionMetadata + { + /// + /// Scopes accepted by this web API. + /// + public string[]? AcceptedScope { get; set; } + + /// + /// Fully qualified name of the configuration key containing the required scopes (separated + /// by spaces). + /// + /// + /// If the appsettings.json file contains a section named "AzureAd", in which + /// a property named "Scopes" contains the required scopes, the attribute on the + /// controller/page/action to protect should be set to the following: + /// + /// [RequiredScopeOrAppPermission(RequiredScopesConfigurationKey="AzureAd:Scopes")] + /// + /// + public string? RequiredScopesConfigurationKey { get; set; } + + /// + /// App permissions accepted by this web API. + /// App permissions appear in the roles claim of the token. + /// + public string[]? AcceptedAppPermission { get; set; } + + /// + /// Fully qualified name of the configuration key containing the required app permissions (separated + /// by spaces). + /// + /// + /// If the appsettings.json file contains a section named "AzureAd", in which + /// a property named "AppPermissions" contains the required app permissions, the attribute on the + /// controller/page/action to protect should be set to the following: + /// + /// [RequiredScopeOrAppPermission(RequiredAppPermissionsConfigurationKey="AzureAd:AppPermissions")] + /// + /// + public string? RequiredAppPermissionsConfigurationKey { get; set; } + + /// + /// Verifies that the web API is called with the right app permissions. + /// If the token obtained for this API is on behalf of the authenticated user does not have + /// any of these in its scope claim, + /// nor in its roles claim, the + /// method updates the HTTP response providing a status code 403 (Forbidden) + /// and writes to the response body a message telling which scopes are expected in the token. + /// + /// Scopes accepted by this web API. + /// App permissions accepted by this web API. + /// When neither the scopes nor app permissions match, the response is a 403 (Forbidden), + /// because the user is authenticated (hence not 401), but not authorized. + /// + /// Add the following attribute on the controller/page/action to protect: + /// + /// + /// [RequiredScopeOrAppPermission(new [] { "access_as_user" }, new [] { "access_as_app" })] + /// + /// + /// and + /// if you want to express the required scopes or app permissions from the configuration. + public RequiredScopeOrAppPermissionAttribute(string[] acceptedScopes, string[] acceptedAppPermissions) + { + AcceptedScope = acceptedScopes ?? throw new ArgumentNullException(nameof(acceptedScopes)); + AcceptedAppPermission = acceptedAppPermissions ?? throw new ArgumentNullException(nameof(acceptedAppPermissions)); + } + + /// + /// Default constructor. + /// + /// + /// + /// [RequiredScopeOrAppPermission(RequiredScopesConfigurationKey="AzureAD:Scope", RequiredAppPermissionsConfigurationKey="AzureAD:AppPermission")] + /// class Controller : BaseController + /// { + /// } + /// + /// + public RequiredScopeOrAppPermissionAttribute() + { + } + } +} diff --git a/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionExtensions.cs b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionExtensions.cs new file mode 100644 index 000000000..118ef011b --- /dev/null +++ b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionExtensions.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.Identity.Web +{ + /// + /// Extensions for building the required scope or app permission attribute during application startup. + /// + public static class RequiredScopeOrAppPermissionExtensions + { + /// + /// This method adds support for the required scope or app permission attribute. It adds a default policy that + /// adds a scope requirement or app permission requirement. + /// This requirement looks for IAuthRequiredScopeOrAppPermissionMetadata on the current endpoint. + /// + /// The services being configured. + /// Services. + public static IServiceCollection AddRequiredScopeOrAppPermissionAuthorization(this IServiceCollection services) + { + services.AddAuthorization(); + + services.TryAddEnumerable(ServiceDescriptor.Singleton, RequireScopeOrAppPermissionOptions>()); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + return services; + } + + /// + /// This method adds metadata to route endpoint to describe required scopes or app permissions. It's the imperative version of + /// the [RequiredScopeOrAppPermission] attribute. + /// + /// Class implementing . + /// To customize the endpoints. + /// Scope. + /// App permission. + /// Builder. + public static TBuilder RequireScopeOrAppPermission(this TBuilder endpointConventionBuilder, string[] scope, string[] appPermission) + where TBuilder : IEndpointConventionBuilder + { + return endpointConventionBuilder.WithMetadata(new RequiredScopeOrAppPermissionMetadata(scope, appPermission)); + } + + private sealed class RequiredScopeOrAppPermissionMetadata : IAuthRequiredScopeMetadata + { + public RequiredScopeOrAppPermissionMetadata(string[] scope, string[] appPermission) + { + AcceptedScope = scope; + AcceptedAppPermission = appPermission; + } + + public string[]? AcceptedScope { get; } + public string[]? AcceptedAppPermission { get; } + + public string? RequiredScopesConfigurationKey { get; } + public string? RequiredAppPermissionsConfigurationKey { get; } + } + } +} diff --git a/src/Microsoft.Identity.Web/Policy/ScopeAuthorizationHandler.cs b/src/Microsoft.Identity.Web/Policy/ScopeAuthorizationHandler.cs index 39fb05d51..a96f05ff7 100644 --- a/src/Microsoft.Identity.Web/Policy/ScopeAuthorizationHandler.cs +++ b/src/Microsoft.Identity.Web/Policy/ScopeAuthorizationHandler.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; diff --git a/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationHandler.cs b/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationHandler.cs new file mode 100644 index 000000000..27bf6ec0c --- /dev/null +++ b/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationHandler.cs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.Identity.Web +{ + /// + /// Scope or app permission authorization handler that needs to be called for a specific requirement type. + /// In this case, . + /// + internal class ScopeOrAppPermissionAuthorizationHandler : AuthorizationHandler + { + private readonly IConfiguration _configuration; + + /// + /// Constructor for the scope authorization handler, which takes a configuration. + /// + /// Configuration. + public ScopeOrAppPermissionAuthorizationHandler(IConfiguration configuration) + { + _configuration = configuration; + } + + /// + /// Makes a decision if authorization is allowed based on a specific requirement. + /// + /// AuthorizationHandlerContext. + /// Scope authorization requirement. + /// Task. + protected override Task HandleRequirementAsync( + AuthorizationHandlerContext context, + ScopeOrAppPermissionAuthorizationRequirement requirement) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (requirement is null) + { + throw new ArgumentNullException(nameof(requirement)); + } + + // The resource is either the HttpContext or the Endpoint directly when used with the + // authorization middleware + var endpoint = context.Resource switch + { + HttpContext httpContext => httpContext.GetEndpoint(), + Endpoint ep => ep, + _ => null, + }; + + var data = endpoint?.Metadata.GetMetadata(); + + IEnumerable? scopes = null; + IEnumerable? appPermissions = null; + + var scopeConfigurationKey = requirement.RequiredScopesConfigurationKey ?? data?.RequiredScopesConfigurationKey; + + if (scopeConfigurationKey != null) + { + scopes = _configuration.GetValue(scopeConfigurationKey)?.Split(' '); + } + + if (scopes is null) + { + scopes = requirement.ScopeAllowedValues ?? data?.AcceptedScope; + } + + var appPermissionConfigurationKey = requirement.RequiredAppPermissionsConfigurationKey ?? data?.RequiredAppPermissionsConfigurationKey; + + if (appPermissionConfigurationKey != null) + { + appPermissions = _configuration.GetValue(appPermissionConfigurationKey)?.Split(' '); + } + + if (appPermissions is null) + { + appPermissions = requirement.AppPermissionAllowedValues ?? data?.AcceptedAppPermission; + } + + // Can't determine what to do without scope or app permission metadata, so proceed + if (scopes is null && appPermissions is null) + { + context.Succeed(requirement); + return Task.CompletedTask; + } + + var scopeClaims = context.User.FindAll(ClaimConstants.Scp) + .Union(context.User.FindAll(ClaimConstants.Scope)) + .ToList(); + + var appPermissionClaims = context.User.FindAll(ClaimConstants.Role) + .Union(context.User.FindAll(ClaimConstants.Roles)) + .ToList(); + + if (!scopeClaims.Any() && !appPermissionClaims.Any()) + { + return Task.CompletedTask; + } + + var hasScope = scopes != null && scopeClaims.SelectMany(s => s.Value.Split(' ')).Intersect(scopes).Any(); + + var hasAppPermission = appPermissions != null && appPermissionClaims.SelectMany(s => s.Value.Split(' ')).Intersect(appPermissions).Any(); + + if (hasScope || hasAppPermission) + { + context.Succeed(requirement); + return Task.CompletedTask; + } + + return Task.CompletedTask; + } + } +} diff --git a/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationRequirement.cs b/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationRequirement.cs new file mode 100644 index 000000000..d4ca91e47 --- /dev/null +++ b/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationRequirement.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; + +namespace Microsoft.Identity.Web +{ + /// + /// Implements an + /// which requires at least one instance of the specified claim type, and, if allowed values are specified, + /// the claim value must be any of the allowed values. + /// + public class ScopeOrAppPermissionAuthorizationRequirement : IAuthorizationRequirement + { + /// + /// Creates a new instance of . + /// + /// The optional list of scope values. + /// + public ScopeOrAppPermissionAuthorizationRequirement( + IEnumerable? scopeAllowedValues = null, + IEnumerable? appPermissionAllowedValues = null) + { + ScopeAllowedValues = scopeAllowedValues; + AppPermissionAllowedValues = appPermissionAllowedValues; + } + + /// + /// Gets the optional list of scope values. + /// + public IEnumerable? ScopeAllowedValues { get; } + + /// + /// Gets the optional list of app permission values. + /// + public IEnumerable? AppPermissionAllowedValues { get; } + + /// + /// Gets the optional list of scope values from configuration. + /// + public string? RequiredScopesConfigurationKey { get; set; } + + /// + /// Gets the optional list of app permission values from configuration. + /// + public string? RequiredAppPermissionsConfigurationKey { get; set; } + + /// + public override string ToString() + { + string value; + + if (ScopeAllowedValues == null && AppPermissionAllowedValues == null) + { + value = string.Empty; + } + else if (ScopeAllowedValues != null && AppPermissionAllowedValues != null) + { + value = $" and `{ClaimConstants.Scp}` or `{ClaimConstants.Scope}` is one of the following values: ({string.Join("|", ScopeAllowedValues)}) or " + + $"TokenValidationParameters.RoleClaimType is one of th following values: ({string.Join("|", AppPermissionAllowedValues)})"; + } + else if (ScopeAllowedValues != null) + { + value = $" and `{ClaimConstants.Scp}` or `{ClaimConstants.Scope}` is one of the following values: ({string.Join("|", ScopeAllowedValues)})"; + } + else + { + value = $" and `{ClaimConstants.Roles}` or `{ClaimConstants.Roles}` is one of the following values: ({string.Join("|", AppPermissionAllowedValues!)})"; + } + + return $"{nameof(ScopeOrAppPermissionAuthorizationRequirement)}:Scope/AppPermission={value}"; + } + } +} diff --git a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs index f64700f9d..cec3265f7 100644 --- a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs +++ b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs @@ -163,11 +163,12 @@ private static void AddMicrosoftIdentityWebApiImplementation( builder.Services.AddHttpClient(); builder.Services.TryAddSingleton(); builder.Services.AddRequiredScopeAuthorization(); + builder.Services.AddRequiredScopeOrAppPermissionAuthorization(); builder.Services.AddOptions(); if (subscribeToJwtBearerMiddlewareDiagnosticsEvents) { - builder.Services.AddSingleton(); + builder.Services.AddTransient(); } // Change the authentication configuration to accommodate the Microsoft identity platform endpoint (v2.0). diff --git a/tests/Microsoft.Identity.Web.Test.LabInfrastructure/LabAuthenticationHelper.cs b/tests/Microsoft.Identity.Web.Test.LabInfrastructure/LabAuthenticationHelper.cs index f97e06b8e..9db7cc8be 100644 --- a/tests/Microsoft.Identity.Web.Test.LabInfrastructure/LabAuthenticationHelper.cs +++ b/tests/Microsoft.Identity.Web.Test.LabInfrastructure/LabAuthenticationHelper.cs @@ -16,7 +16,7 @@ public static class LabAuthenticationHelper private static readonly InMemoryTokenCache s_staticCache = new InMemoryTokenCache(); private const string LabAccessConfidentialClientId = "16dab2ba-145d-4b1b-8569-bf4b9aed4dc8"; private const string LabAccessPublicClientId = "3c1e0e0d-b742-45ba-a35e-01c664e14b16"; - private const string LabAccessThumbPrint = "378938210C976692D7F523B8C4FFBB645D17CE92"; + private const string LabAccessThumbPrint = "4E87313FD450985A10BC0F14A292859F2DCD6CD3"; private const string DataFileName = "data.txt"; private static readonly LabAccessAuthenticationType s_defaultAuthType = LabAccessAuthenticationType.ClientCertificate; private static readonly string s_secret; diff --git a/tests/Microsoft.Identity.Web.Test/Resource/JwtBearerEventsClaimsValidationTests.cs b/tests/Microsoft.Identity.Web.Test/Resource/JwtBearerEventsClaimsValidationTests.cs index a14fc3e80..bd4f5e9f2 100644 --- a/tests/Microsoft.Identity.Web.Test/Resource/JwtBearerEventsClaimsValidationTests.cs +++ b/tests/Microsoft.Identity.Web.Test/Resource/JwtBearerEventsClaimsValidationTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; diff --git a/tests/Microsoft.Identity.Web.Test/Resource/RequiredScopeOrAppPermissionPolicyTests.cs b/tests/Microsoft.Identity.Web.Test/Resource/RequiredScopeOrAppPermissionPolicyTests.cs new file mode 100644 index 000000000..6f6ae311c --- /dev/null +++ b/tests/Microsoft.Identity.Web.Test/Resource/RequiredScopeOrAppPermissionPolicyTests.cs @@ -0,0 +1,202 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Configuration.Memory; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Identity.Web.Resource; +using Microsoft.Identity.Web.Test.Common; +using Xunit; + +namespace Microsoft.Identity.Web.Test.Resource +{ + [RequiredScopeOrAppPermission(new string[] { "access_as_user" }, new string[] { "access_as_app" })] + public class RequiredScopeOrAppPermissionPolicyTests + { + private IServiceProvider _provider; + private const string ConfigSectionName = "AzureAd"; + private const string MultipleAppPermissions = "access_as_app_read_only access_as_app"; + private const string AppPermission = "access_as_app"; + private const string Scope = "user.read"; + private const string PolicyName = "foo"; + + [Theory] + [InlineData(ClaimConstants.Role)] + [InlineData(ClaimConstants.Role, true)] + [InlineData(ClaimConstants.Roles)] + [InlineData(ClaimConstants.Roles, true)] + [RequiredScopeOrAppPermission(RequiredAppPermissionsConfigurationKey = "AzureAd:AppPermission")] + public async void VerifyAppHasAnyAcceptedAppPermission_TestAsync( + string claimType, + bool withConfig = false) + { + // Arrange + var authorizationService = BuildAuthorizationService( + PolicyName, + AppPermission, + null, + withConfig); + + var user = new ClaimsPrincipal( + new ClaimsIdentity(new Claim[] { new Claim(claimType, AppPermission) })); + + // Act + var allowed = await authorizationService.AuthorizeAsync(user, PolicyName).ConfigureAwait(false); + + // Assert + Assert.True(allowed.Succeeded); + } + + [Theory] + [InlineData(ClaimConstants.Role)] + [InlineData(ClaimConstants.Role, true)] + [InlineData(ClaimConstants.Roles)] + [InlineData(ClaimConstants.Roles, true)] + public async void VerifyAppHasAnyAcceptedAppPermission_OneAppPermissionMatches_TestAsync( + string claimType, + bool withConfig = false) + { + // Arrange + var authorizationService = BuildAuthorizationService( + PolicyName, + AppPermission, + null, + withConfig); + + var user = new ClaimsPrincipal( + new ClaimsIdentity(new Claim[] { new Claim(claimType, MultipleAppPermissions) })); + + // Act + var allowed = await authorizationService.AuthorizeAsync(user, PolicyName).ConfigureAwait(false); + + // Assert + Assert.True(allowed.Succeeded); + } + + [Fact] + public async void VerifyAppHasAnyAcceptedAppPermission_WithMismatchAppPermissionTest_FailsAsync() + { + // Arrange + var authorizationService = BuildAuthorizationService( + PolicyName, + AppPermission, + null); + + var user = new ClaimsPrincipal( + new ClaimsIdentity(new Claim[] { new Claim(ClaimConstants.Role, "access_as_app2") })); + + // Act + var allowed = await authorizationService.AuthorizeAsync(user, PolicyName).ConfigureAwait(false); + + // Assert + Assert.False(allowed.Succeeded); + } + + [Fact] + public async void VerifyAppHasAnyAcceptedAppPermission_RequiredAppPermissionMissingAsync() + { + // Arrange + var authorizationService = BuildAuthorizationService( + PolicyName, + null, + null); + + var user = new ClaimsPrincipal( + new ClaimsIdentity(new Claim[] { new Claim(ClaimConstants.Role, AppPermission) })); + + // Act + var allowed = await authorizationService.AuthorizeAsync(user, PolicyName).ConfigureAwait(false); + + // Assert + Assert.True(allowed.Succeeded); + } + + [Fact] + public async void IncorrectPolicyName_FailsAsync() + { + // Arrange + var authorizationService = BuildAuthorizationService( + "foobar", + AppPermission, + null); + + var user = new ClaimsPrincipal( + new ClaimsIdentity(new Claim[] { new Claim(ClaimConstants.Role, AppPermission) })); + + // Act & Assert + var exception = await Assert.ThrowsAsync(() => authorizationService.AuthorizeAsync(user, PolicyName)).ConfigureAwait(false); + Assert.Equal("No policy found: foo.", exception.Message); + } + + [Fact] + public async void VerifyAppHasAnyAcceptedScopeOrAppPermission_TestAsync() + { + // Arrange + var authorizationService = BuildAuthorizationService( + PolicyName, + AppPermission, + Scope); + + var user = new ClaimsPrincipal( + new ClaimsIdentity(new Claim[] { new Claim(ClaimConstants.Role, AppPermission), new Claim(ClaimConstants.Scp, Scope) })); + + // Act + var allowed = await authorizationService.AuthorizeAsync(user, PolicyName).ConfigureAwait(false); + + // Assert + Assert.True(allowed.Succeeded); + } + + private IAuthorizationService BuildAuthorizationService( + string policy, + string appPermission, + string scope, + bool withConfig = false) + { + var configAsDictionary = new Dictionary() + { + { ConfigSectionName, null }, + { $"{ConfigSectionName}:Instance", TestConstants.AadInstance }, + { $"{ConfigSectionName}:TenantId", TestConstants.TenantIdAsGuid }, + { $"{ConfigSectionName}:ClientId", TestConstants.ClientId }, + { $"{ConfigSectionName}:AppPermission", appPermission }, + { $"{ConfigSectionName}:Scope", scope }, + }; + var memoryConfigSource = new MemoryConfigurationSource { InitialData = configAsDictionary }; + + IHostBuilder hostBuilder = Host.CreateDefaultBuilder() + .ConfigureHostConfiguration(configBuilder => + { + configBuilder.Add(memoryConfigSource); + configBuilder.Build().GetSection(ConfigSectionName); + }) + .ConfigureServices(services => + { + services.AddAuthorization(options => + { + options.AddPolicy(policy, policyBuilder => + { + if (withConfig) + { + policyBuilder.Requirements.Add(new ScopeOrAppPermissionAuthorizationRequirement() { RequiredAppPermissionsConfigurationKey = $"{ConfigSectionName}:AppPermission" }); + } + else + { + policyBuilder.RequireScopeOrAppPermission(scope?.Split(' '), appPermission?.Split(' ')); + } + }); + }); + services.AddLogging(); + services.AddOptions(); + services.AddSingleton(); + services.AddRequiredScopeOrAppPermissionAuthorization(); + }); + _provider = hostBuilder.Build().Services; + return _provider.GetRequiredService(); + } + } +} diff --git a/tests/Microsoft.Identity.Web.Test/WebApiExtensionsTests.cs b/tests/Microsoft.Identity.Web.Test/WebApiExtensionsTests.cs index 7f26cfd0c..07bf32429 100644 --- a/tests/Microsoft.Identity.Web.Test/WebApiExtensionsTests.cs +++ b/tests/Microsoft.Identity.Web.Test/WebApiExtensionsTests.cs @@ -125,7 +125,7 @@ private void AddMicrosoftIdentityWebApi_TestCommon(IServiceCollection services, Assert.Contains(services, s => s.ServiceType == typeof(IConfigureOptions)); Assert.Contains(services, s => s.ServiceType == typeof(IJwtBearerMiddlewareDiagnostics)); Assert.Equal(ServiceLifetime.Singleton, services.First(s => s.ServiceType == typeof(MicrosoftIdentityIssuerValidatorFactory)).Lifetime); - Assert.Equal(ServiceLifetime.Singleton, services.First(s => s.ServiceType == typeof(IJwtBearerMiddlewareDiagnostics)).Lifetime); + Assert.Equal(ServiceLifetime.Transient, services.First(s => s.ServiceType == typeof(IJwtBearerMiddlewareDiagnostics)).Lifetime); // JWT options added correctly var configuredJwtOptions = provider.GetService>() as IConfigureNamedOptions; diff --git a/tests/aspnet-mvc/OwinWebApi/OwinWebApi.csproj b/tests/aspnet-mvc/OwinWebApi/OwinWebApi.csproj index 83dcc35cb..660332439 100644 --- a/tests/aspnet-mvc/OwinWebApi/OwinWebApi.csproj +++ b/tests/aspnet-mvc/OwinWebApi/OwinWebApi.csproj @@ -193,7 +193,7 @@ - + @@ -221,4 +221,4 @@ --> - \ No newline at end of file + diff --git a/tools/app-provisioning-tool/app-provisioning-lib/ProjectDescriptions/dotnet-web.json b/tools/app-provisioning-tool/app-provisioning-lib/ProjectDescriptions/dotnet-web.json index 2f3af845c..674215c64 100644 --- a/tools/app-provisioning-tool/app-provisioning-lib/ProjectDescriptions/dotnet-web.json +++ b/tools/app-provisioning-tool/app-provisioning-lib/ProjectDescriptions/dotnet-web.json @@ -87,7 +87,30 @@ ] }, { - "FileRelativePath": "Startup.cs", + "FileRelativePath": "Startup.cs", + "Properties": [ + { + "MatchAny": [ ".AddMicrosoftGraph", ".AddMicrosoftGraphBeta" ], + "Sets": "CallsMicrosoftGraph" + }, + { + "MatchAny": [ ".EnableTokenAcquisitionToCallDownstreamApi", ".AddDownstreamWebApi" ], + "Sets": "CallsDownstreamApi" + }, + { + "Property": "\"AzureAdB2C\"", + "Represents": "Application.ConfigurationSection", + "Sets": "IsB2C" + }, + { + "Property": "\"AzureAd\"", + "Represents": "Application.ConfigurationSection", + "Sets": "IsAAD" + } + ] + }, + { + "FileRelativePath": "Program.cs", "Properties": [ { "MatchAny": [ ".AddMicrosoftGraph", ".AddMicrosoftGraphBeta" ], diff --git a/tools/app-provisioning-tool/app-provisioning-lib/ProjectDescriptions/dotnet-webapi.json b/tools/app-provisioning-tool/app-provisioning-lib/ProjectDescriptions/dotnet-webapi.json index 6439dbb35..464754990 100644 --- a/tools/app-provisioning-tool/app-provisioning-lib/ProjectDescriptions/dotnet-webapi.json +++ b/tools/app-provisioning-tool/app-provisioning-lib/ProjectDescriptions/dotnet-webapi.json @@ -15,7 +15,16 @@ ], "ConfigurationProperties": [ { - "FileRelativePath": "Startup.cs", + "FileRelativePath": "Startup.cs", + "Properties": [ + { + "MatchAny": [ ".AddAzureAdBearer", ".AddMicrosoftIdentityWebApi", ".AddMicrosoftIdentityWebApiAuthentication" ], + "Sets": "HasAuthentication" + } + ] + }, + { + "FileRelativePath": "Program.cs", "Properties": [ { "MatchAny": [ ".AddAzureAdBearer", ".AddMicrosoftIdentityWebApi", ".AddMicrosoftIdentityWebApiAuthentication" ], @@ -24,4 +33,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/tools/app-provisioning-tool/app-provisioning-lib/ProjectDescriptions/dotnet-webapp.json b/tools/app-provisioning-tool/app-provisioning-lib/ProjectDescriptions/dotnet-webapp.json index 29d1181cf..20873bdba 100644 --- a/tools/app-provisioning-tool/app-provisioning-lib/ProjectDescriptions/dotnet-webapp.json +++ b/tools/app-provisioning-tool/app-provisioning-lib/ProjectDescriptions/dotnet-webapp.json @@ -18,7 +18,16 @@ ], "ConfigurationProperties": [ { - "FileRelativePath": "Startup.cs", + "FileRelativePath": "Startup.cs", + "Properties": [ + { + "MatchAny": [ ".AddAzureAD", ".AddMicrosoftIdentityWebApp", ".AddMicrosoftIdentityWebAppAuthentication" ], + "Sets": "HasAuthentication" + } + ] + }, + { + "FileRelativePath": "Program.cs", "Properties": [ { "MatchAny": [ ".AddAzureAD", ".AddMicrosoftIdentityWebApp", ".AddMicrosoftIdentityWebAppAuthentication" ], @@ -27,4 +36,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/tools/app-provisioning-tool/app-provisioning-tool/Readme.txt b/tools/app-provisioning-tool/app-provisioning-tool/Readme.txt index 2a2ad2bd7..3a2b21fc9 100644 --- a/tools/app-provisioning-tool/app-provisioning-tool/Readme.txt +++ b/tools/app-provisioning-tool/app-provisioning-tool/Readme.txt @@ -1,5 +1,5 @@ -Install -dotnet tool install --global --add-source ./nupkg msIdentityApp +Install (run from the tools\app-provisioning-tool\app-provisioning-tool folder): +dotnet tool install --global --add-source ./nupkg msidentity-app-sync To un install -dotnet tool uninstall --global msidentityapp +dotnet tool uninstall --global msidentity-app-sync diff --git a/tools/app-provisioning-tool/app-provisioning-tool/msidentity-app-sync.csproj b/tools/app-provisioning-tool/app-provisioning-tool/msidentity-app-sync.csproj index 5e7c190e8..6e1e2c2a5 100644 --- a/tools/app-provisioning-tool/app-provisioning-tool/msidentity-app-sync.csproj +++ b/tools/app-provisioning-tool/app-provisioning-tool/msidentity-app-sync.csproj @@ -2,6 +2,7 @@ Exe + 1.0.1 netcoreapp3.1;net5.0 enable Microsoft.Identity.App