/// <inheritdoc/> public async Task <AuthenticationResult> GetAuthenticationResultForUserAsync( IEnumerable <string> scopes, string?tenantId = null, string?userFlow = null, ClaimsPrincipal?user = null, TokenAcquisitionOptions?tokenAcquisitionOptions = null) { string? idToken = AppServicesAuthenticationInformation.GetIdToken(CurrentHttpContext?.Request?.Headers !); ClaimsPrincipal?userClaims = AppServicesAuthenticationInformation.GetUser(CurrentHttpContext?.Request?.Headers !); string accessToken = await GetAccessTokenForUserAsync(scopes, tenantId, userFlow, user, tokenAcquisitionOptions).ConfigureAwait(false); string expiration = userClaims.FindFirstValue("exp"); DateTimeOffset dateTimeOffset = (expiration != null) ? DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiration, CultureInfo.InvariantCulture)) : DateTimeOffset.Now; AuthenticationResult authenticationResult = new AuthenticationResult( accessToken, isExtendedLifeTimeToken: false, userClaims?.GetDisplayName(), dateTimeOffset, dateTimeOffset, userClaims?.GetTenantId(), userClaims != null ? new Account(userClaims) : null, idToken, scopes, tokenAcquisitionOptions != null ? tokenAcquisitionOptions.CorrelationId : Guid.Empty); return(authenticationResult); }
/// <inheritdoc/> public Task <string> GetAccessTokenForUserAsync( IEnumerable <string> scopes, string?tenantId = null, string?userFlow = null, ClaimsPrincipal?user = null, TokenAcquisitionOptions?tokenAcquisitionOptions = null) { var httpContext = CurrentHttpContext; string accessToken; if (httpContext != null) { // Need to lock due to https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?#do-not-access-httpcontext-from-multiple-threads lock (httpContext) { accessToken = GetAccessToken(httpContext.Request.Headers); } } else { accessToken = string.Empty; } return(Task.FromResult(accessToken)); }
/// <summary> /// Gets an access token for a downstream API on behalf of the user described by its claimsPrincipal. /// </summary> /// <param name="application"><see cref="IConfidentialClientApplication"/>.</param> /// <param name="claimsPrincipal">Claims principal for the user on behalf of whom to get a token.</param> /// <param name="scopes">Scopes for the downstream API to call.</param> /// <param name="authority">(optional) Authority based on a specific tenant for which to acquire a token to access the scopes /// on behalf of the user described in the claimsPrincipal.</param> /// <param name="userFlow">Azure AD B2C user flow to target.</param> /// <param name="tokenAcquisitionOptions">Options passed-in to create the token acquisition object which calls into MSAL .NET.</param> private async Task <AuthenticationResult> GetAuthenticationResultForWebAppWithAccountFromCacheAsync( IConfidentialClientApplication application, ClaimsPrincipal?claimsPrincipal, IEnumerable <string> scopes, string?authority, string?userFlow = null, TokenAcquisitionOptions?tokenAcquisitionOptions = null) { IAccount?account = null; if (_microsoftIdentityOptions.IsB2C && !string.IsNullOrEmpty(userFlow)) { string?nameIdentifierId = claimsPrincipal?.GetNameIdentifierId(); string?utid = claimsPrincipal?.GetHomeTenantId(); string?b2cAccountIdentifier = string.Format(CultureInfo.InvariantCulture, "{0}-{1}.{2}", nameIdentifierId, userFlow, utid); account = await application.GetAccountAsync(b2cAccountIdentifier).ConfigureAwait(false); } else { string?accountIdentifier = claimsPrincipal?.GetMsalAccountId(); if (accountIdentifier != null) { account = await application.GetAccountAsync(accountIdentifier).ConfigureAwait(false); } } return(await GetAuthenticationResultForWebAppWithAccountFromCacheAsync( application, account, scopes, authority, userFlow, tokenAcquisitionOptions).ConfigureAwait(false)); }
/// <inheritdoc/> public Task <AuthenticationResult> GetAuthenticationResultForAppAsync( string scope, string?authenticationScheme = null, string?tenant = null, TokenAcquisitionOptions?tokenAcquisitionOptions = null) { throw new NotImplementedException(); }
/// <summary> /// 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. /// </summary> /// <param name="scope">The scope requested to access a protected API. For this flow (client credentials), the scope /// should be of the form "{ResourceIdUri/.default}" for instance <c>https://management.azure.net/.default</c> or, for Microsoft /// Graph, <c>https://graph.microsoft.com/.default</c> 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).</param> /// <param name="tenant">Enables overriding of the tenant/account for the same identity. This is useful /// for multi tenant apps or daemons.</param> /// <param name="tokenAcquisitionOptions">Options passed-in to create the token acquisition object which calls into MSAL .NET.</param> /// <returns>An access token for the app itself, based on its scopes.</returns> public async Task <string> GetAccessTokenForAppAsync( string scope, string?tenant = null, TokenAcquisitionOptions?tokenAcquisitionOptions = null) { AuthenticationResult authResult = await GetAuthenticationResultForAppAsync(scope, tenant, tokenAcquisitionOptions).ConfigureAwait(false); return(authResult.AccessToken); }
/// <inheritdoc/> public async Task <string> GetAccessTokenForUserAsync( IEnumerable <string> scopes, string?tenantId = null, string?userFlow = null, ClaimsPrincipal?user = null, TokenAcquisitionOptions?tokenAcquisitionOptions = null) { string accessToken = GetAccessToken(CurrentHttpContext?.Request.Headers); return(await Task.FromResult(accessToken).ConfigureAwait(false)); }
/// <summary> /// 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 <a href='https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow'>on-behalf-of flow</a> /// in web APIs, for the user account that is ascertained from claims provided in the <see cref="HttpContext.User"/> /// instance of the current HttpContext. /// </summary> /// <param name="scopes">Scopes to request for the downstream API to call.</param> /// <param name="tenantId">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.</param> /// <param name="userFlow">Azure AD B2C user flow to target.</param> /// <param name="user">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.</param> /// <param name="tokenAcquisitionOptions">Options passed-in to create the token acquisition options object which calls into MSAL .NET.</param> /// <returns>An access token to call the downstream API and populated with this downstream API's scopes.</returns> /// <remarks>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.</remarks> public async Task <AuthenticationResult> GetAuthenticationResultForUserAsync( IEnumerable <string> scopes, string?tenantId = null, string?userFlow = null, ClaimsPrincipal?user = null, TokenAcquisitionOptions?tokenAcquisitionOptions = null) { if (scopes == null) { throw new ArgumentNullException(nameof(scopes)); } user = await GetAuthenticatedUserAsync(user).ConfigureAwait(false); _application = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false); string authority = CreateAuthorityBasedOnTenantIfProvided(_application, tenantId); AuthenticationResult?authenticationResult; try { // Access token will return if call is from a web API authenticationResult = await GetAuthenticationResultForWebApiToCallDownstreamApiAsync( _application, authority, scopes, tokenAcquisitionOptions).ConfigureAwait(false); if (authenticationResult != null) { return(authenticationResult); } // If access token is null, this is a web app return(await GetAuthenticationResultForWebAppWithAccountFromCacheAsync( _application, user, scopes, authority, userFlow) .ConfigureAwait(false)); } catch (MsalUiRequiredException ex) { // GetAccessTokenForUserAsync is an abstraction that can be called from a web app or a web API _logger.LogInformation(ex.Message); // Case of the web app: we let the MsalUiRequiredException be caught by the // AuthorizeForScopesAttribute exception filter so that the user can consent, do 2FA, etc ... throw new MicrosoftIdentityWebChallengeUserException(ex, scopes.ToArray(), userFlow); } }
private async Task <AuthenticationResult?> GetAuthenticationResultForWebApiToCallDownstreamApiAsync( IConfidentialClientApplication application, string authority, IEnumerable <string> scopes, TokenAcquisitionOptions?tokenAcquisitionOptions) { try { // In web API, validatedToken will not be null JwtSecurityToken?validatedToken = CurrentHttpContext?.GetTokenUsedToCallWebAPI(); // Case of web APIs: we need to do an on-behalf-of flow, with the token used to call the API if (validatedToken != null) { // In the case the token is a JWE (encrypted token), we use the decrypted token. string tokenUsedToCallTheWebApi = validatedToken.InnerToken == null ? validatedToken.RawData : validatedToken.InnerToken.RawData; var builder = application .AcquireTokenOnBehalfOf( scopes.Except(_scopesRequestedByMsal), new UserAssertion(tokenUsedToCallTheWebApi)) .WithSendX5C(_microsoftIdentityOptions.SendX5C) .WithAuthority(authority); if (tokenAcquisitionOptions != null) { builder.WithExtraQueryParameters(tokenAcquisitionOptions.ExtraQueryParameters); builder.WithCorrelationId(tokenAcquisitionOptions.CorrelationId); builder.WithForceRefresh(tokenAcquisitionOptions.ForceRefresh); builder.WithClaims(tokenAcquisitionOptions.Claims); if (tokenAcquisitionOptions.PoPConfiguration != null) { builder.WithProofOfPossession(tokenAcquisitionOptions.PoPConfiguration); } } return(await builder.ExecuteAsync() .ConfigureAwait(false)); } return(null); } catch (MsalUiRequiredException ex) { _logger.LogInformation( string.Format( CultureInfo.InvariantCulture, LogMessages.ErrorAcquiringTokenForDownstreamWebApi, ex.Message)); throw; } }
/// <summary> /// 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. /// </summary> /// <param name="scope">The scope requested to access a protected API. For this flow (client credentials), the scope /// should be of the form "{ResourceIdUri/.default}" for instance <c>https://management.azure.net/.default</c> or, for Microsoft /// Graph, <c>https://graph.microsoft.com/.default</c> 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).</param> /// <param name="tenant">Enables overriding of the tenant/account for the same identity. This is useful /// for multi tenant apps or daemons.</param> /// <param name="tokenAcquisitionOptions">Options passed-in to create the token acquisition object which calls into MSAL .NET.</param> /// <returns>An access token for the app itself, based on its scopes.</returns> public async Task <string> GetAccessTokenForAppAsync( string scope, string?tenant = null, TokenAcquisitionOptions?tokenAcquisitionOptions = null) { if (string.IsNullOrEmpty(scope)) { throw new ArgumentNullException(nameof(scope)); } if (!scope.EndsWith("/.default", true, CultureInfo.InvariantCulture)) { throw new ArgumentException(IDWebErrorMessage.ClientCredentialScopeParameterShouldEndInDotDefault, nameof(scope)); } if (string.IsNullOrEmpty(tenant)) { tenant = _applicationOptions.TenantId; } if (!string.IsNullOrEmpty(tenant) && _metaTenantIdentifiers.Contains(tenant)) { throw new ArgumentException(IDWebErrorMessage.ClientCredentialTenantShouldBeTenanted, nameof(tenant)); } // Use MSAL to get the right token to call the API _application = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false); string authority = CreateAuthorityBasedOnTenantIfProvided(_application, tenant); AuthenticationResult result; var builder = _application .AcquireTokenForClient(new string[] { scope }.Except(_scopesRequestedByMsal)) .WithSendX5C(_microsoftIdentityOptions.SendX5C) .WithAuthority(authority); if (tokenAcquisitionOptions != null) { builder.WithExtraQueryParameters(tokenAcquisitionOptions.ExtraQueryParameters); builder.WithCorrelationId(tokenAcquisitionOptions.CorrelationId); builder.WithForceRefresh(tokenAcquisitionOptions.ForceRefresh); builder.WithClaims(tokenAcquisitionOptions.Claims); } result = await builder.ExecuteAsync() .ConfigureAwait(false); return(result.AccessToken); }
/// <summary> /// Gets an access token for a downstream API on behalf of the user whose account is passed as an argument. /// </summary> /// <param name="application"><see cref="IConfidentialClientApplication"/>.</param> /// <param name="account">User IAccount for which to acquire a token. /// See <see cref="Microsoft.Identity.Client.AccountId.Identifier"/>.</param> /// <param name="scopes">Scopes for the downstream API to call.</param> /// <param name="authority">Authority based on a specific tenant for which to acquire a token to access the scopes /// on behalf of the user.</param> /// <param name="userFlow">Azure AD B2C user flow.</param> /// <param name="tokenAcquisitionOptions">Options passed-in to create the token acquisition object which calls into MSAL .NET.</param> private async Task <AuthenticationResult> GetAuthenticationResultForWebAppWithAccountFromCacheAsync( IConfidentialClientApplication application, IAccount?account, IEnumerable <string> scopes, string?authority, string?userFlow = null, TokenAcquisitionOptions?tokenAcquisitionOptions = null) { if (scopes == null) { throw new ArgumentNullException(nameof(scopes)); } var builder = application .AcquireTokenSilent(scopes.Except(_scopesRequestedByMsal), account) .WithSendX5C(_microsoftIdentityOptions.SendX5C); if (tokenAcquisitionOptions != null) { builder.WithExtraQueryParameters(tokenAcquisitionOptions.ExtraQueryParameters); builder.WithCorrelationId(tokenAcquisitionOptions.CorrelationId); builder.WithForceRefresh(tokenAcquisitionOptions.ForceRefresh); builder.WithClaims(tokenAcquisitionOptions.Claims); if (tokenAcquisitionOptions.PoPConfiguration != null) { builder.WithProofOfPossession(tokenAcquisitionOptions.PoPConfiguration); } } // Acquire an access token as a B2C authority if (_microsoftIdentityOptions.IsB2C) { string b2cAuthority = application.Authority.Replace( new Uri(application.Authority).PathAndQuery, $"/{ClaimConstants.Tfp}/{_microsoftIdentityOptions.Domain}/{userFlow ?? _microsoftIdentityOptions.DefaultUserFlow}"); builder.WithB2CAuthority(b2cAuthority) .WithSendX5C(_microsoftIdentityOptions.SendX5C); } else { builder.WithAuthority(authority); } return(await builder.ExecuteAsync() .ConfigureAwait(false)); }
/// <summary> /// 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 <a href='https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow'>on-behalf-of flow</a> /// in web APIs, for the user account that is ascertained from the claims provided in the <see cref="HttpContext.User"/> /// instance of the current HttpContext. /// </summary> /// <param name="scopes">Scopes to request for the downstream API to call.</param> /// <param name="tenantId">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.</param> /// <param name="userFlow">Azure AD B2C user flow to target.</param> /// <param name="user">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.</param> /// <param name="tokenAcquisitionOptions">Options passed-in to create the token acquisition object which calls into MSAL .NET.</param> /// <returns>An access token to call the downstream API and populated with this downstream API's scopes.</returns> /// <remarks>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.</remarks> public async Task <string> GetAccessTokenForUserAsync( IEnumerable <string> scopes, string?tenantId = null, string?userFlow = null, ClaimsPrincipal?user = null, TokenAcquisitionOptions?tokenAcquisitionOptions = null) { AuthenticationResult result = await GetAuthenticationResultForUserAsync( scopes, tenantId, userFlow, user, tokenAcquisitionOptions).ConfigureAwait(false); return(result.AccessToken); }
/// <inheritdoc/> public async Task <string> GetAccessTokenForAppAsync( string scope, string?tenant = null, TokenAcquisitionOptions?tokenAcquisitionOptions = null) { // We could use MSI if (scope is null) { throw new ArgumentNullException(nameof(scope)); } var app = GetOrCreateApplication(); AuthenticationResult result = await app.AcquireTokenForClient(new string[] { scope }) .ExecuteAsync() .ConfigureAwait(false); return(result.AccessToken); }
/// <inheritdoc/> #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public async Task <AuthenticationResult> GetAuthenticationResultForUserAsync(IEnumerable <string> scopes, string?tenantId = null, string?userFlow = null, ClaimsPrincipal?user = null, TokenAcquisitionOptions?tokenAcquisitionOptions = null) { throw new NotImplementedException(); }