diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs index 4e1dc687e9..b512fc78a7 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs @@ -2,13 +2,15 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Identity.Client.Cache.Items; using Microsoft.Identity.Client.Internal; using Microsoft.Identity.Client.Utils; namespace Microsoft.Identity.Client.AuthScheme.Bearer { - internal class BearerAuthenticationOperation : IAuthenticationOperation + internal class BearerAuthenticationOperation : IAuthenticationOperation2 { internal const string BearerTokenType = "bearer"; @@ -25,6 +27,12 @@ public void FormatResult(AuthenticationResult authenticationResult) // no-op } + public Task FormatResultAsync(AuthenticationResult authenticationResult, CancellationToken cancellationToken = default) + { + // no-op, return completed task + return Task.CompletedTask; + } + public IReadOnlyDictionary GetTokenRequestParams() { // ESTS issues Bearer tokens by default, no need for any extra params diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs index 1ed04ea6e2..739ca6b1aa 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Identity.Client.Cache.Items; namespace Microsoft.Identity.Client.AuthScheme { @@ -54,7 +53,7 @@ public interface IAuthenticationOperation /// Creates the access token that goes into an Authorization HTTP header. /// void FormatResult(AuthenticationResult authenticationResult); - + /// /// Expected to match the token_type parameter returned by ESTS. Used to disambiguate /// between ATs of different types (e.g. Bearer and PoP) when loading from cache etc. diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation2.cs b/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation2.cs new file mode 100644 index 0000000000..1bb1eaf932 --- /dev/null +++ b/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation2.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Identity.Client.AuthScheme +{ + /// + /// This is an extensibility API and should only be used by SDKs. + /// Enhanced version of IAuthenticationOperation that supports asynchronous token formatting. + /// Used to modify the experience depending on the type of token asked with async capabilities. + /// + public interface IAuthenticationOperation2 : IAuthenticationOperation + { + /// + /// Will be invoked instead of IAuthenticationOperation.FormatResult + /// + Task FormatResultAsync(AuthenticationResult authenticationResult, CancellationToken cancellationToken = default); + } +} diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs index a5533ce2f6..c7fbd7ebb4 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs @@ -4,13 +4,15 @@ using System.Collections.Generic; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Identity.Client.Internal; using Microsoft.Identity.Client.OAuth2; using Microsoft.Identity.Client.Utils; namespace Microsoft.Identity.Client.AuthScheme.PoP { - internal class MtlsPopAuthenticationOperation : IAuthenticationOperation + internal class MtlsPopAuthenticationOperation : IAuthenticationOperation2 { private readonly X509Certificate2 _mtlsCert; @@ -36,11 +38,6 @@ public IReadOnlyDictionary GetTokenRequestParams() }; } - public void FormatResult(AuthenticationResult authenticationResult) - { - //no-op - } - private static string ComputeX5tS256KeyId(X509Certificate2 certificate) { // Extract the raw bytes of the certificate’s public key. @@ -55,5 +52,15 @@ private static string ComputeX5tS256KeyId(X509Certificate2 certificate) return Base64UrlHelpers.Encode(hash); } } + + public Task FormatResultAsync(AuthenticationResult authenticationResult, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + public void FormatResult(AuthenticationResult authenticationResult) + { + // no-op + } } } diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs index a2723fe9b9..934d42d688 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs @@ -6,6 +6,8 @@ using System.Globalization; using System.Security.Cryptography; using System.Text; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Identity.Client.AppConfig; using Microsoft.Identity.Client.Cache.Items; using Microsoft.Identity.Client.Internal; @@ -21,7 +23,7 @@ namespace Microsoft.Identity.Client.AuthScheme.PoP { - internal class PopAuthenticationOperation : IAuthenticationOperation + internal class PopAuthenticationOperation : IAuthenticationOperation2 { private readonly PoPAuthenticationConfiguration _popAuthenticationConfiguration; private readonly IPoPCryptoProvider _popCryptoProvider; @@ -86,6 +88,14 @@ public void FormatResult(AuthenticationResult authenticationResult) authenticationResult.AccessToken = popToken; } + public Task FormatResultAsync(AuthenticationResult authenticationResult, CancellationToken cancellationToken = default) + { + // For now, PoP token creation is synchronous, so we wrap the sync method + // Future enhancement could make crypto operations truly async + FormatResult(authenticationResult); + return Task.CompletedTask; + } + private JObject CreateBody(string accessToken) { var publicKeyJwk = JToken.Parse(_popCryptoProvider.CannonicalPublicKeyJwk); diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs index c81f411f41..e9e6e7a388 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.Identity.Client.Cache.Items; using Microsoft.Identity.Client.Internal; @@ -15,7 +16,7 @@ namespace Microsoft.Identity.Client.AuthScheme.PoP //Authentication Scheme used when MSAL Broker and pop are used together. //Tokens acquired from brokers will not be saved in the local ache and MSAL will not search the local cache during silent authentication. //This is because tokens are cached in the broker instead so MSAL will rely on the broker's cache for silent requests. - internal class PopBrokerAuthenticationOperation : IAuthenticationOperation + internal class PopBrokerAuthenticationOperation : IAuthenticationOperation2 { public int TelemetryTokenType => TelemetryTokenTypeConstants.Pop; @@ -25,9 +26,14 @@ internal class PopBrokerAuthenticationOperation : IAuthenticationOperation public string AccessTokenType => Constants.PoPTokenType; + public Task FormatResultAsync(AuthenticationResult authenticationResult, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + public void FormatResult(AuthenticationResult authenticationResult) { - //no-op + // no-op } public IReadOnlyDictionary GetTokenRequestParams() diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs index cc64c2db61..5a5a7cd04e 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs @@ -3,13 +3,15 @@ using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Identity.Client.Cache.Items; using Microsoft.Identity.Client.Internal; using Microsoft.Identity.Client.OAuth2; namespace Microsoft.Identity.Client.AuthScheme.SSHCertificates { - internal class SSHCertAuthenticationOperation : IAuthenticationOperation + internal class SSHCertAuthenticationOperation : IAuthenticationOperation2 { internal const string SSHCertTokenType = "ssh-cert"; private readonly string _jwk; @@ -40,6 +42,11 @@ public SSHCertAuthenticationOperation(string keyId, string jwk) public string KeyId { get; } + public Task FormatResultAsync(AuthenticationResult authenticationResult, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + public void FormatResult(AuthenticationResult authenticationResult) { // no-op diff --git a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs index c75c921375..c165fa5fcf 100644 --- a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs +++ b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using System.Globalization; using System.Security.Claims; +using System.Threading; using System.Threading.Tasks; using Microsoft.Identity.Client.AuthScheme; using Microsoft.Identity.Client.Cache; @@ -21,7 +22,7 @@ namespace Microsoft.Identity.Client /// public partial class AuthenticationResult { - private readonly IAuthenticationOperation _authenticationScheme; + private IAuthenticationOperation _authenticationScheme; /// /// Constructor meant to help application developers test their apps. Allows mocking of authentication flows. @@ -126,19 +127,76 @@ public AuthenticationResult( } - internal AuthenticationResult( + /// + /// This method must be used by the product code to create an instance. + /// It calls IAuthenticationOperation.FormatResult or FormatResultAsync on the authentication scheme + /// + internal static async Task CreateAsync( MsalAccessTokenCacheItem msalAccessTokenCacheItem, MsalIdTokenCacheItem msalIdTokenCacheItem, IAuthenticationOperation authenticationScheme, - Guid correlationID, + Guid correlationId, TokenSource tokenSource, ApiEvent apiEvent, Account account, string spaAuthCode, - IReadOnlyDictionary additionalResponseParameters) + IReadOnlyDictionary additionalResponseParameters, + CancellationToken cancellationToken = default) { - _authenticationScheme = authenticationScheme ?? throw new ArgumentNullException(nameof(authenticationScheme)); + if (authenticationScheme == null) + { + throw new ArgumentNullException(nameof(authenticationScheme)); + } + + // Create the AuthenticationResult without calling FormatResult in constructor + var result = new AuthenticationResult( + msalAccessTokenCacheItem, + msalIdTokenCacheItem, + correlationId, + tokenSource, + apiEvent, + account, + spaAuthCode, + additionalResponseParameters, + authenticationScheme); + + // Apply token formatting (async if supported, sync otherwise) + var measuredResultDuration = await StopwatchService.MeasureCodeBlockAsync(async () => + { + if (authenticationScheme is IAuthenticationOperation2 asyncAuthScheme) + { + await asyncAuthScheme.FormatResultAsync(result, cancellationToken).ConfigureAwait(false); + } + else + { + authenticationScheme.FormatResult(result); + } + }).ConfigureAwait(false); + + // Update telemetry metadata + result.AuthenticationResultMetadata.DurationCreatingExtendedTokenInUs = measuredResultDuration.Microseconds; + result.AuthenticationResultMetadata.TelemetryTokenType = authenticationScheme.TelemetryTokenType; + + return result; + } + + //Default constructor for testing + internal AuthenticationResult() { } + /// + /// This is to help CreateAsync + /// + private AuthenticationResult( + MsalAccessTokenCacheItem msalAccessTokenCacheItem, + MsalIdTokenCacheItem msalIdTokenCacheItem, + Guid correlationID, + TokenSource tokenSource, + ApiEvent apiEvent, + Account account, + string spaAuthCode, + IReadOnlyDictionary additionalResponseParameters, + IAuthenticationOperation authenticationScheme) + { string homeAccountId = msalAccessTokenCacheItem?.HomeAccountId ?? msalIdTokenCacheItem?.HomeAccountId; @@ -163,7 +221,7 @@ internal AuthenticationResult( TenantId = msalIdTokenCacheItem?.IdToken?.TenantId; IdToken = msalIdTokenCacheItem?.Secret; SpaAuthCode = spaAuthCode; - + _authenticationScheme = authenticationScheme; CorrelationId = correlationID; ApiEvent = apiEvent; AuthenticationResultMetadata = new AuthenticationResultMetadata(tokenSource); @@ -189,20 +247,8 @@ internal AuthenticationResult( AccessToken = msalAccessTokenCacheItem.Secret; } - - var measuredResultDuration = StopwatchService.MeasureCodeBlock(() => - { - //Important: only call this at the end - authenticationScheme.FormatResult(this); - }); - - AuthenticationResultMetadata.DurationCreatingExtendedTokenInUs = measuredResultDuration.Microseconds; - AuthenticationResultMetadata.TelemetryTokenType = authenticationScheme.TelemetryTokenType; } - //Default constructor for testing - internal AuthenticationResult() { } - /// /// Access Token that can be used as a bearer token to access protected web APIs /// diff --git a/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs b/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs index d622bbbae7..84e1830893 100644 --- a/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs +++ b/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Identity.Client.AuthScheme; using Microsoft.Identity.Client.Cache.Items; using Microsoft.Identity.Client.Internal; @@ -9,7 +11,7 @@ namespace Microsoft.Identity.Client.Extensibility { - internal class ExternalBoundTokenScheme : IAuthenticationOperation + internal class ExternalBoundTokenScheme : IAuthenticationOperation2 { private readonly string _keyId; private readonly string _tokenType; @@ -28,6 +30,11 @@ public ExternalBoundTokenScheme(string keyId, string expectedTokenTypeFromEsts = public string AccessTokenType => _tokenType; + public Task FormatResultAsync(AuthenticationResult authenticationResult, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + public void FormatResult(AuthenticationResult authenticationResult) { // no-op diff --git a/src/client/Microsoft.Identity.Client/Extensibility/MsalAuthenticationExtension.cs b/src/client/Microsoft.Identity.Client/Extensibility/MsalAuthenticationExtension.cs index cfe839ba71..dc9cc3be6c 100644 --- a/src/client/Microsoft.Identity.Client/Extensibility/MsalAuthenticationExtension.cs +++ b/src/client/Microsoft.Identity.Client/Extensibility/MsalAuthenticationExtension.cs @@ -20,7 +20,7 @@ public class MsalAuthenticationExtension public Func OnBeforeTokenRequestHandler { get; set; } /// - /// Enables the developer to provide a custom authentication extension. + /// Important: IAuthenticationOperation2 exists, for asynchronous token formatting. /// public IAuthenticationOperation AuthenticationOperation { get; set; } diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/ByRefreshTokenRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/ByRefreshTokenRequest.cs index b4bc477a4f..891d001379 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/ByRefreshTokenRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/ByRefreshTokenRequest.cs @@ -37,7 +37,7 @@ protected override async Task ExecuteAsync(CancellationTok throw new MsalServiceException(msalTokenResponse.Error, msalTokenResponse.ErrorDescription, null); } - return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse).ConfigureAwait(false); + return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse, cancellationToken).ConfigureAwait(false); } private static Dictionary GetBodyParameters(string refreshTokenSecret) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs index 34948dcb71..a8e6440a5d 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs @@ -76,7 +76,7 @@ protected override async Task ExecuteAsync(CancellationTok // No access token or cached access token needs to be refreshed if (cachedAccessTokenItem != null) { - authResult = CreateAuthenticationResultFromCache(cachedAccessTokenItem); + authResult = await CreateAuthenticationResultFromCacheAsync(cachedAccessTokenItem).ConfigureAwait(false); try { @@ -101,7 +101,7 @@ protected override async Task ExecuteAsync(CancellationTok } catch (MsalServiceException e) { - return await HandleTokenRefreshErrorAsync(e, cachedAccessTokenItem).ConfigureAwait(false); + return await HandleTokenRefreshErrorAsync(e, cachedAccessTokenItem, cancellationToken).ConfigureAwait(false); } } else @@ -128,7 +128,7 @@ private async Task GetAccessTokenAsync( if (ServiceBundle.Config.AppTokenProvider == null) { MsalTokenResponse msalTokenResponse = await SendTokenRequestAsync(GetBodyParameters(), cancellationToken).ConfigureAwait(false); - return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse).ConfigureAwait(false); + return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse, cancellationToken).ConfigureAwait(false); } // Get a token from the app provider delegate @@ -164,7 +164,7 @@ private async Task GetAccessTokenAsync( else { logger.Verbose(() => "[ClientCredentialRequest] Checking for a cached access token."); - authResult = CreateAuthenticationResultFromCache(cachedAccessTokenItem); + authResult = await CreateAuthenticationResultFromCacheAsync(cachedAccessTokenItem).ConfigureAwait(false); } } @@ -196,7 +196,7 @@ private async Task SendTokenRequestToAppTokenProviderAsync tokenResponse.Scope = appTokenProviderParameters.Scopes.AsSingleString(); tokenResponse.CorrelationId = appTokenProviderParameters.CorrelationId; - AuthenticationResult authResult = await CacheTokenResponseAndCreateAuthenticationResultAsync(tokenResponse) + AuthenticationResult authResult = await CacheTokenResponseAndCreateAuthenticationResultAsync(tokenResponse, cancellationToken) .ConfigureAwait(false); return authResult; @@ -274,9 +274,9 @@ private void MarkAccessTokenAsCacheHit() /// /// /// - private AuthenticationResult CreateAuthenticationResultFromCache(MsalAccessTokenCacheItem cachedAccessTokenItem) + private Task CreateAuthenticationResultFromCacheAsync(MsalAccessTokenCacheItem cachedAccessTokenItem) { - AuthenticationResult authResult = new AuthenticationResult( + return AuthenticationResult.CreateAsync( cachedAccessTokenItem, null, AuthenticationRequestParameters.AuthenticationScheme, @@ -286,7 +286,6 @@ private AuthenticationResult CreateAuthenticationResultFromCache(MsalAccessToken account: null, spaAuthCode: null, additionalResponseParameters: null); - return authResult; } /// diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/ConfidentialAuthCodeRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/ConfidentialAuthCodeRequest.cs index 768b8979ae..b974c3afd2 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/ConfidentialAuthCodeRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/ConfidentialAuthCodeRequest.cs @@ -28,7 +28,7 @@ protected override async Task ExecuteAsync(CancellationTok { await ResolveAuthorityAsync().ConfigureAwait(false); var msalTokenResponse = await SendTokenRequestAsync(GetBodyParameters(), cancellationToken).ConfigureAwait(false); - return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse).ConfigureAwait(false); + return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse, cancellationToken).ConfigureAwait(false); } private Dictionary GetBodyParameters() diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/DeviceCodeRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/DeviceCodeRequest.cs index 8a9a7460ef..838e82861f 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/DeviceCodeRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/DeviceCodeRequest.cs @@ -58,7 +58,7 @@ protected override async Task ExecuteAsync(CancellationTok await _deviceCodeParameters.DeviceCodeResultCallback(deviceCodeResult).ConfigureAwait(false); var msalTokenResponse = await WaitForTokenResponseAsync(deviceCodeResult, cancellationToken).ConfigureAwait(false); - return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse).ConfigureAwait(false); + return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse, cancellationToken).ConfigureAwait(false); } private async Task WaitForTokenResponseAsync( diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/IntegratedWindowsAuthRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/IntegratedWindowsAuthRequest.cs index 4b8e4ba2f0..cb758df3e5 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/IntegratedWindowsAuthRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/IntegratedWindowsAuthRequest.cs @@ -64,7 +64,7 @@ protected override async Task ExecuteAsync(CancellationTok GetAdditionalBodyParameters(userAssertion), cancellationToken) .ConfigureAwait(false); - return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse).ConfigureAwait(false); + return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse, cancellationToken).ConfigureAwait(false); } protected override KeyValuePair? GetCcsHeader(IDictionary additionalBodyParameters) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/Interactive/InteractiveRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/Interactive/InteractiveRequest.cs index c1d96ea2c2..aa989d9d1f 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/Interactive/InteractiveRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/Interactive/InteractiveRequest.cs @@ -107,7 +107,7 @@ protected override async Task ExecuteAsync( tokenResponse = await GetTokenResponseAsync(cancellationToken) .ConfigureAwait(false); } - return await CacheTokenResponseAndCreateAuthenticationResultAsync(tokenResponse) + return await CacheTokenResponseAndCreateAuthenticationResultAsync(tokenResponse, cancellationToken) .ConfigureAwait(false); } #endregion diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/ManagedIdentityAuthRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/ManagedIdentityAuthRequest.cs index cb441baa80..02d858792a 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/ManagedIdentityAuthRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/ManagedIdentityAuthRequest.cs @@ -50,7 +50,7 @@ protected override async Task ExecuteAsync(CancellationTok // No access token or cached access token needs to be refreshed if (cachedAccessTokenItem != null) { - authResult = CreateAuthenticationResultFromCache(cachedAccessTokenItem); + authResult = await CreateAuthenticationResultFromCacheAsync(cachedAccessTokenItem, cancellationToken).ConfigureAwait(false); logger.Info("[ManagedIdentityRequest] Access token retrieved from cache."); @@ -79,7 +79,7 @@ protected override async Task ExecuteAsync(CancellationTok } catch (MsalServiceException e) { - return await HandleTokenRefreshErrorAsync(e, cachedAccessTokenItem).ConfigureAwait(false); + return await HandleTokenRefreshErrorAsync(e, cachedAccessTokenItem, cancellationToken).ConfigureAwait(false); } } else @@ -129,7 +129,7 @@ private async Task GetAccessTokenAsync( // Check the cache again after acquiring the semaphore in case the previous request cached a new token. if (cachedAccessTokenItem != null) { - authResult = CreateAuthenticationResultFromCache(cachedAccessTokenItem); + authResult = await CreateAuthenticationResultFromCacheAsync(cachedAccessTokenItem, cancellationToken).ConfigureAwait(false); } else { @@ -163,7 +163,7 @@ await managedIdentityClient var msalTokenResponse = MsalTokenResponse.CreateFromManagedIdentityResponse(managedIdentityResponse); msalTokenResponse.Scope = AuthenticationRequestParameters.Scope.AsSingleString(); - return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse).ConfigureAwait(false); + return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse, cancellationToken).ConfigureAwait(false); } private async Task GetCachedAccessTokenAsync() @@ -180,19 +180,19 @@ private async Task GetCachedAccessTokenAsync() return null; } - private AuthenticationResult CreateAuthenticationResultFromCache(MsalAccessTokenCacheItem cachedAccessTokenItem) + private Task CreateAuthenticationResultFromCacheAsync( + MsalAccessTokenCacheItem cachedAccessTokenItem, CancellationToken cancellationToken) { - AuthenticationResult authResult = new AuthenticationResult( - cachedAccessTokenItem, - null, - AuthenticationRequestParameters.AuthenticationScheme, - AuthenticationRequestParameters.RequestContext.CorrelationId, - TokenSource.Cache, - AuthenticationRequestParameters.RequestContext.ApiEvent, - account: null, - spaAuthCode: null, - additionalResponseParameters: null); - return authResult; + return AuthenticationResult.CreateAsync( + msalAccessTokenCacheItem: cachedAccessTokenItem, + msalIdTokenCacheItem: null, authenticationScheme: AuthenticationRequestParameters.AuthenticationScheme, + correlationId: AuthenticationRequestParameters.RequestContext.CorrelationId, + tokenSource: TokenSource.Cache, + apiEvent: AuthenticationRequestParameters.RequestContext.ApiEvent, + account: null, + spaAuthCode: null, + additionalResponseParameters: null, + cancellationToken: cancellationToken); } protected override KeyValuePair? GetCcsHeader(IDictionary additionalBodyParameters) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/OnBehalfOfRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/OnBehalfOfRequest.cs index ced6345178..f978034031 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/OnBehalfOfRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/OnBehalfOfRequest.cs @@ -82,7 +82,7 @@ protected override async Task ExecuteAsync(CancellationTok AuthenticationRequestParameters.RequestContext.ApiEvent.IsAccessTokenCacheHit = true; Metrics.IncrementTotalAccessTokensFromCache(); - authResult = new AuthenticationResult( + authResult = await AuthenticationResult.CreateAsync( cachedAccessToken, cachedIdToken, AuthenticationRequestParameters.AuthenticationScheme, @@ -91,7 +91,8 @@ protected override async Task ExecuteAsync(CancellationTok AuthenticationRequestParameters.RequestContext.ApiEvent, account, spaAuthCode: null, - additionalResponseParameters: null); + additionalResponseParameters: null, + cancellationToken: cancellationToken).ConfigureAwait(false); } else { @@ -145,7 +146,7 @@ protected override async Task ExecuteAsync(CancellationTok } catch (MsalServiceException e) { - return await HandleTokenRefreshErrorAsync(e, cachedAccessToken).ConfigureAwait(false); + return await HandleTokenRefreshErrorAsync(e, cachedAccessToken, cancellationToken).ConfigureAwait(false); } } @@ -180,7 +181,7 @@ private async Task RefreshRtOrFetchNewAccessTokenAsync(Can var msalTokenResponse = await SilentRequestHelper.RefreshAccessTokenAsync(cachedRefreshToken, this, AuthenticationRequestParameters, cancellationToken) .ConfigureAwait(false); - return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse).ConfigureAwait(false); + return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse, cancellationToken).ConfigureAwait(false); } if (AuthenticationRequestParameters.ApiId == ApiEvent.ApiIds.AcquireTokenInLongRunningObo) @@ -216,7 +217,7 @@ private async Task FetchNewAccessTokenAsync(CancellationTo logger.Info("[OBO request] This is an on behalf of request for a service principal as no client info returned in the token response."); } - return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse).ConfigureAwait(false); + return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse, cancellationToken).ConfigureAwait(false); } private Dictionary GetBodyParameters() diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index 0ac5bd3627..7017e2513f 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -312,7 +312,7 @@ private AssertionType GetAssertionType() return AssertionType.None; } - protected async Task CacheTokenResponseAndCreateAuthenticationResultAsync(MsalTokenResponse msalTokenResponse) + protected async Task CacheTokenResponseAndCreateAuthenticationResultAsync(MsalTokenResponse msalTokenResponse, CancellationToken cancellationToken = default) { // developer passed in user object. AuthenticationRequestParameters.RequestContext.Logger.Info("Checking client info returned from the server.."); @@ -339,7 +339,7 @@ protected async Task CacheTokenResponseAndCreateAuthentica var idtItem = tuple.Item2; Account account = tuple.Item3; - return new AuthenticationResult( + return await AuthenticationResult.CreateAsync( atItem, idtItem, AuthenticationRequestParameters.AuthenticationScheme, @@ -348,7 +348,8 @@ protected async Task CacheTokenResponseAndCreateAuthentica AuthenticationRequestParameters.RequestContext.ApiEvent, account, msalTokenResponse.SpaAuthCode, - msalTokenResponse.CreateExtensionDataStringMap()); + msalTokenResponse.CreateExtensionDataStringMap(), + cancellationToken).ConfigureAwait(false); } protected virtual void ValidateAccountIdentifiers(ClientInfo fromServer) @@ -517,7 +518,10 @@ private void LogReturnedToken(AuthenticationResult result) } } - internal async Task HandleTokenRefreshErrorAsync(MsalServiceException e, MsalAccessTokenCacheItem cachedAccessTokenItem) + internal async Task HandleTokenRefreshErrorAsync( + MsalServiceException e, + MsalAccessTokenCacheItem cachedAccessTokenItem, + CancellationToken cancellationToken) { var logger = AuthenticationRequestParameters.RequestContext.Logger; @@ -530,7 +534,7 @@ internal async Task HandleTokenRefreshErrorAsync(MsalServi var idToken = await CacheManager.GetIdTokenCacheItemAsync(cachedAccessTokenItem).ConfigureAwait(false); var account = await CacheManager.GetAccountAssociatedWithAccessTokenAsync(cachedAccessTokenItem).ConfigureAwait(false); - return new AuthenticationResult( + return await AuthenticationResult.CreateAsync( cachedAccessTokenItem, idToken, AuthenticationRequestParameters.AuthenticationScheme, @@ -539,7 +543,8 @@ internal async Task HandleTokenRefreshErrorAsync(MsalServi AuthenticationRequestParameters.RequestContext.ApiEvent, account, spaAuthCode: null, - additionalResponseParameters: null); + additionalResponseParameters: null, + cancellationToken: cancellationToken).ConfigureAwait(false); } logger.Warning("Either the exception does not indicate a problem with AAD or the token cache does not have an AT that is usable. "); diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/BrokerSilentStrategy.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/BrokerSilentStrategy.cs index 27850f3e7a..bdfa3c268a 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/BrokerSilentStrategy.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/BrokerSilentStrategy.cs @@ -55,7 +55,7 @@ public async Task ExecuteAsync(CancellationToken cancellat { ValidateResponseFromBroker(response); Metrics.IncrementTotalAccessTokensFromBroker(); - return await _silentRequest.CacheTokenResponseAndCreateAuthenticationResultAsync(response).ConfigureAwait(false); + return await _silentRequest.CacheTokenResponseAndCreateAuthenticationResultAsync(response, cancellationToken).ConfigureAwait(false); } return null; diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/CacheSilentStrategy.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/CacheSilentStrategy.cs index 976b0dd913..8a6fcdd48e 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/CacheSilentStrategy.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/CacheSilentStrategy.cs @@ -54,7 +54,7 @@ public async Task ExecuteAsync(CancellationToken cancellat + cachedAccessTokenItem.RefreshOn.HasValue); AuthenticationRequestParameters.RequestContext.ApiEvent.IsAccessTokenCacheHit = true; Metrics.IncrementTotalAccessTokensFromCache(); - authResult = await CreateAuthenticationResultAsync(cachedAccessTokenItem).ConfigureAwait(false); + authResult = await CreateAuthenticationResultAsync(cachedAccessTokenItem, cancellationToken).ConfigureAwait(false); } else { @@ -113,7 +113,7 @@ public async Task ExecuteAsync(CancellationToken cancellat if (cachedAccessTokenItem != null && e.IsRetryable) { logger.Info("Returning existing access token. It is not expired, but should be refreshed. "); - return await CreateAuthenticationResultAsync(cachedAccessTokenItem).ConfigureAwait(false); + return await CreateAuthenticationResultAsync(cachedAccessTokenItem, cancellationToken).ConfigureAwait(false); } logger.Warning("Failed to refresh the RT and cannot use existing AT (expired or missing). "); @@ -152,15 +152,15 @@ private async Task RefreshRtOrFailAsync(CancellationToken msalTokenResponse = await SilentRequestHelper.RefreshAccessTokenAsync(appRefreshToken, _silentRequest, AuthenticationRequestParameters, cancellationToken) .ConfigureAwait(false); } - return await _silentRequest.CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse).ConfigureAwait(false); + return await _silentRequest.CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse, cancellationToken).ConfigureAwait(false); } - private async Task CreateAuthenticationResultAsync(MsalAccessTokenCacheItem cachedAccessTokenItem) + private async Task CreateAuthenticationResultAsync(MsalAccessTokenCacheItem cachedAccessTokenItem, CancellationToken ct) { var msalIdTokenItem = await CacheManager.GetIdTokenCacheItemAsync(cachedAccessTokenItem).ConfigureAwait(false); var account = await CacheManager.GetAccountAssociatedWithAccessTokenAsync(cachedAccessTokenItem).ConfigureAwait(false); - return new AuthenticationResult( + return await AuthenticationResult.CreateAsync( cachedAccessTokenItem, msalIdTokenItem, AuthenticationRequestParameters.AuthenticationScheme, @@ -169,7 +169,8 @@ private async Task CreateAuthenticationResultAsync(MsalAcc AuthenticationRequestParameters.RequestContext.ApiEvent, account, spaAuthCode: null, - additionalResponseParameters: null); + additionalResponseParameters: null, + cancellationToken: ct).ConfigureAwait(false); } private async Task TryGetTokenUsingFociAsync(CancellationToken cancellationToken) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/SilentRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/SilentRequest.cs index 5f4037d8bf..f1a0baa9e3 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/SilentRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/SilentRequest.cs @@ -89,9 +89,9 @@ protected override async Task ExecuteAsync(CancellationTok } } - internal new Task CacheTokenResponseAndCreateAuthenticationResultAsync(MsalTokenResponse response) + internal new Task CacheTokenResponseAndCreateAuthenticationResultAsync(MsalTokenResponse response, CancellationToken cancellationToken = default) { - return base.CacheTokenResponseAndCreateAuthenticationResultAsync(response); + return base.CacheTokenResponseAndCreateAuthenticationResultAsync(response, cancellationToken); } //internal for test diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/UsernamePasswordRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/UsernamePasswordRequest.cs index 1b049b6223..dddf9bdcb1 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/UsernamePasswordRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/UsernamePasswordRequest.cs @@ -61,7 +61,7 @@ protected override async Task ExecuteAsync(CancellationTok throw new MsalServiceException(MsalError.JsonParseError, MsalErrorMessage.JsonParseErrorMessage, ex); } - return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse).ConfigureAwait(false); + return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse, cancellationToken).ConfigureAwait(false); } private async Task GetTokenResponseAsync(CancellationToken cancellationToken) diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt index 50d9f12956..61c3f23849 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ const Microsoft.Identity.Client.MsalError.InvalidManagedIdentityIdType = "invalid_managed_identity_id_type" -> string const Microsoft.Identity.Client.MsalError.MissingManagedIdentityEnvVar = "missing_managed_identity_env_var" -> string +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2 +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt index 50d9f12956..61c3f23849 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ const Microsoft.Identity.Client.MsalError.InvalidManagedIdentityIdType = "invalid_managed_identity_id_type" -> string const Microsoft.Identity.Client.MsalError.MissingManagedIdentityEnvVar = "missing_managed_identity_env_var" -> string +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2 +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt index 50d9f12956..61c3f23849 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ const Microsoft.Identity.Client.MsalError.InvalidManagedIdentityIdType = "invalid_managed_identity_id_type" -> string const Microsoft.Identity.Client.MsalError.MissingManagedIdentityEnvVar = "missing_managed_identity_env_var" -> string +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2 +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt index 50d9f12956..61c3f23849 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ const Microsoft.Identity.Client.MsalError.InvalidManagedIdentityIdType = "invalid_managed_identity_id_type" -> string const Microsoft.Identity.Client.MsalError.MissingManagedIdentityEnvVar = "missing_managed_identity_env_var" -> string +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2 +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt index 50d9f12956..61c3f23849 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ const Microsoft.Identity.Client.MsalError.InvalidManagedIdentityIdType = "invalid_managed_identity_id_type" -> string const Microsoft.Identity.Client.MsalError.MissingManagedIdentityEnvVar = "missing_managed_identity_env_var" -> string +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2 +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt index 50d9f12956..61c3f23849 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ const Microsoft.Identity.Client.MsalError.InvalidManagedIdentityIdType = "invalid_managed_identity_id_type" -> string const Microsoft.Identity.Client.MsalError.MissingManagedIdentityEnvVar = "missing_managed_identity_env_var" -> string +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2 +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/AuthenticationOperationTests.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/AuthenticationOperationTests.cs index c72ca62deb..fd041d1938 100644 --- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/AuthenticationOperationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/AuthenticationOperationTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using Microsoft.Identity.Client; using Microsoft.Identity.Client.ApiConfig.Parameters; @@ -190,6 +191,56 @@ public async Task WrongTokenType_Async() } } + private class TestOperation : IAuthenticationOperation2 + { + public string AuthorizationHeaderPrefix => "TestToken"; + public string KeyId => "keyid"; + public IReadOnlyDictionary GetTokenRequestParams() + { + return new Dictionary() { { "tokenParam", "tokenParamValue" } }; + } + public void FormatResult(AuthenticationResult authenticationResult) + { + Assert.Fail("should not be called, FormatResultAsync should be called"); + } + + public Task FormatResultAsync(AuthenticationResult authenticationResult, CancellationToken cancellationToken = default) + { + authenticationResult.AccessToken = "IAuthenticationOperation2" + authenticationResult.AccessToken; + return Task.CompletedTask; + } + + public int TelemetryTokenType => 5; // Extension + public string AccessTokenType => "bearer"; + } + + [TestMethod] + public async Task IAsyncOperation2_Async() + { + // Arrang + using (var httpManager = new MockHttpManager()) + { + httpManager.AddInstanceDiscoveryMockHandler(); + + var cca = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) + .WithExperimentalFeatures() + .WithClientSecret(TestConstants.ClientSecret) + .WithHttpManager(httpManager) + .BuildConcrete(); + + httpManager.AddTokenResponse(TokenResponseType.Valid_ClientCredentials); + + // Act + var result = await cca.AcquireTokenForClient(TestConstants.s_scope) + .WithAuthenticationOperation(new TestOperation()) + .ExecuteAsync().ConfigureAwait(false); + + Assert.IsTrue(result.AccessToken.StartsWith("IAuthenticationOperation2")); + Assert.AreEqual($"TestToken {result.AccessToken}", result.CreateAuthorizationHeader() ); + + } + } + [TestMethod] public async Task MissingAccessTokenTypeInResponse_Throws_Async() { diff --git a/tests/Microsoft.Identity.Test.Unit/pop/PopAuthenticationOperationTests.cs b/tests/Microsoft.Identity.Test.Unit/pop/PopAuthenticationOperationTests.cs index 71178fa9ad..221317a3ec 100644 --- a/tests/Microsoft.Identity.Test.Unit/pop/PopAuthenticationOperationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/pop/PopAuthenticationOperationTests.cs @@ -49,7 +49,7 @@ public void NullArgsTest() } [TestMethod] - public void ValidatePopRequestAndToken() + public async Task ValidatePopRequestAndToken() { using (var harness = CreateTestHarness()) { @@ -70,7 +70,17 @@ public void ValidatePopRequestAndToken() // Act PopAuthenticationOperation authenticationScheme = new PopAuthenticationOperation(popConfig, harness.ServiceBundle); var tokenParams = authenticationScheme.GetTokenRequestParams(); - AuthenticationResult ar = new AuthenticationResult(msalAccessTokenCacheItem, null, authenticationScheme, Guid.NewGuid(), TokenSource.IdentityProvider, default, default, default, default); + AuthenticationResult ar = await AuthenticationResult.CreateAsync( + msalAccessTokenCacheItem, + null, + authenticationScheme, + Guid.NewGuid(), + TokenSource.IdentityProvider, + default, + default, + default, + default, + default).ConfigureAwait(false); JwtSecurityToken decodedPopToken = new JwtSecurityToken(ar.AccessToken); // Assert