public void JitterIsAddedToRefreshOn() { var at = TokenCacheHelper.CreateAccessTokenItem(); var refreshOnFromCache = DateTimeOffset.UtcNow - TimeSpan.FromMinutes(10); at = at.WithRefreshOn(refreshOnFromCache); Func <Task <AuthenticationResult> > fetchAction = () => { return(Task.FromResult <AuthenticationResult>(null)); }; List <DateTimeOffset?> refreshOnWithJitterList = new List <DateTimeOffset?>(); for (int i = 1; i <= 10; i++) { SilentRequestHelper.NeedsRefresh(at, out DateTimeOffset? refreshOnWithJitter); refreshOnWithJitterList.Add(refreshOnWithJitter); Assert.IsTrue(refreshOnWithJitter.HasValue); CoreAssert.IsWithinRange( refreshOnFromCache, refreshOnWithJitter.Value, TimeSpan.FromSeconds(Constants.DefaultJitterRangeInSeconds)); } Assert.IsTrue(refreshOnWithJitterList.Distinct().Count() >= 8, "Jitter is random, so we can only have 1-2 identical values"); }
private async Task <AuthenticationResult> RefreshRtOrFetchNewAccessTokenAsync(CancellationToken cancellationToken) { var logger = AuthenticationRequestParameters.RequestContext.Logger; if (IsLongRunningObo()) { AuthenticationRequestParameters.RequestContext.Logger.Info("[OBO request] Long-running OBO flow, trying to refresh using an refresh token flow."); // Look for a refresh token MsalRefreshTokenCacheItem cachedRefreshToken = await CacheManager.FindRefreshTokenAsync().ConfigureAwait(false); // If a refresh token is not found, fetch a new access token if (cachedRefreshToken != null) { logger.Info("[OBO request] Found a refresh token"); if (!string.IsNullOrEmpty(cachedRefreshToken.RawClientInfo)) { var clientInfo = ClientInfo.CreateFromJson(cachedRefreshToken.RawClientInfo); _ccsRoutingHint = CoreHelpers.GetCcsClientInfoHint( clientInfo.UniqueObjectIdentifier, clientInfo.UniqueTenantIdentifier); } else { logger.Info("[OBO request] No client info associated with RT. This is OBO for a Service Principal."); } var msalTokenResponse = await SilentRequestHelper.RefreshAccessTokenAsync(cachedRefreshToken, this, AuthenticationRequestParameters, cancellationToken) .ConfigureAwait(false); return(await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse).ConfigureAwait(false)); } if (AcquireTokenInLongRunningOboWasCalled()) { AuthenticationRequestParameters.RequestContext.Logger.Error("[OBO request] AcquireTokenInLongRunningProcess was called and no access or refresh tokens were found in the cache."); throw new MsalClientException(MsalError.OboCacheKeyNotInCacheError, MsalErrorMessage.OboCacheKeyNotInCache); } AuthenticationRequestParameters.RequestContext.Logger.Info("[OBO request] No Refresh Token was found in the cache. Fetching OBO token from ESTS"); } else { logger.Info("[OBO request] Normal OBO flow, skipping to fetching access token via OBO flow."); } return(await FetchNewAccessTokenAsync(cancellationToken).ConfigureAwait(false)); }
private async Task <AuthenticationResult> RefreshRtOrFailAsync(CancellationToken cancellationToken) { // Try FOCI first MsalTokenResponse msalTokenResponse = await TryGetTokenUsingFociAsync(cancellationToken) .ConfigureAwait(false); // Normal, non-FOCI flow if (msalTokenResponse == null) { // Look for a refresh token MsalRefreshTokenCacheItem appRefreshToken = await FindRefreshTokenOrFailAsync() .ConfigureAwait(false); msalTokenResponse = await SilentRequestHelper.RefreshAccessTokenAsync(appRefreshToken, _silentRequest, AuthenticationRequestParameters, cancellationToken) .ConfigureAwait(false); } return(await _silentRequest.CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse).ConfigureAwait(false)); }
private async Task <AuthenticationResult> RefreshRtOrFetchNewAccessTokenAsync(CancellationToken cancellationToken) { // Look for a refresh token MsalRefreshTokenCacheItem appRefreshToken = await CacheManager.FindRefreshTokenAsync().ConfigureAwait(false); // If a refresh token is not found, fetch a new access token if (appRefreshToken != null) { var clientInfo = ClientInfo.CreateFromJson(appRefreshToken.RawClientInfo); _ccsRoutingHint = CoreHelpers.GetCcsClientInfoHint(clientInfo.UniqueObjectIdentifier, clientInfo.UniqueTenantIdentifier); var msalTokenResponse = await SilentRequestHelper.RefreshAccessTokenAsync(appRefreshToken, this, AuthenticationRequestParameters, cancellationToken) .ConfigureAwait(false); return(await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse).ConfigureAwait(false)); } AuthenticationRequestParameters.RequestContext.Logger.Info("[OBO request] No Refresh Token was found in the cache. Fetching OBO token from ESTS"); return(await FetchNewAccessTokenAsync(cancellationToken).ConfigureAwait(false)); }
protected override async Task <AuthenticationResult> ExecuteAsync(CancellationToken cancellationToken) { if (AuthenticationRequestParameters.Scope == null || AuthenticationRequestParameters.Scope.Count == 0) { throw new MsalClientException( MsalError.ScopesRequired, MsalErrorMessage.ScopesRequired); } MsalAccessTokenCacheItem cachedAccessTokenItem = null; var logger = AuthenticationRequestParameters.RequestContext.Logger; CacheRefreshReason cacheInfoTelemetry = CacheRefreshReason.NotApplicable; AuthenticationResult authResult = null; if (AuthenticationRequestParameters.Authority is AadAuthority aadAuthority && aadAuthority.IsCommonOrOrganizationsTenant()) { logger.Error(MsalErrorMessage.ClientCredentialWrongAuthority); } if (!_clientParameters.ForceRefresh && string.IsNullOrEmpty(AuthenticationRequestParameters.Claims)) { cachedAccessTokenItem = await CacheManager.FindAccessTokenAsync().ConfigureAwait(false); if (cachedAccessTokenItem != null) { AuthenticationRequestParameters.RequestContext.ApiEvent.IsAccessTokenCacheHit = true; Metrics.IncrementTotalAccessTokensFromCache(); authResult = new AuthenticationResult( cachedAccessTokenItem, null, AuthenticationRequestParameters.AuthenticationScheme, AuthenticationRequestParameters.RequestContext.CorrelationId, TokenSource.Cache, AuthenticationRequestParameters.RequestContext.ApiEvent, null); } else { if (AuthenticationRequestParameters.RequestContext.ApiEvent.CacheInfo != CacheRefreshReason.Expired) { cacheInfoTelemetry = CacheRefreshReason.NoCachedAccessToken; } } } else { cacheInfoTelemetry = CacheRefreshReason.ForceRefreshOrClaims; logger.Info("Skipped looking for an Access Token in the cache because ForceRefresh or Claims were set. "); } if (AuthenticationRequestParameters.RequestContext.ApiEvent.CacheInfo == CacheRefreshReason.NotApplicable) { AuthenticationRequestParameters.RequestContext.ApiEvent.CacheInfo = cacheInfoTelemetry; } // No AT in the cache or AT needs to be refreshed try { if (cachedAccessTokenItem == null) { authResult = await FetchNewAccessTokenAsync(cancellationToken).ConfigureAwait(false); } else { var shouldRefresh = SilentRequestHelper.NeedsRefresh(cachedAccessTokenItem); // may fire a request to get a new token in the background if (shouldRefresh) { AuthenticationRequestParameters.RequestContext.ApiEvent.CacheInfo = CacheRefreshReason.ProactivelyRefreshed; SilentRequestHelper.ProcessFetchInBackground( cachedAccessTokenItem, () => FetchNewAccessTokenAsync(cancellationToken), logger); } } return(authResult); } catch (MsalServiceException e) { return(await HandleTokenRefreshErrorAsync(e, cachedAccessTokenItem).ConfigureAwait(false)); } }
private async Task <MsalTokenResponse> TryGetTokenUsingFociAsync(CancellationToken cancellationToken) { if (!ServiceBundle.PlatformProxy.GetFeatureFlags().IsFociEnabled) { return(null); } var logger = AuthenticationRequestParameters.RequestContext.Logger; // If the app was just added to the family, the app metadata will reflect this // after the first RT exchanged. bool?isFamilyMember = await CacheManager.IsAppFociMemberAsync(TheOnlyFamilyId).ConfigureAwait(false); if (isFamilyMember.HasValue && !isFamilyMember.Value) { AuthenticationRequestParameters.RequestContext.Logger.Verbose( "[FOCI] App is not part of the family, skipping FOCI. "); return(null); } logger.Verbose("[FOCI] App is part of the family or unknown, looking for FRT. "); var familyRefreshToken = await CacheManager.FindFamilyRefreshTokenAsync(TheOnlyFamilyId).ConfigureAwait(false); logger.Verbose("[FOCI] FRT found? " + (familyRefreshToken != null)); if (familyRefreshToken != null) { try { MsalTokenResponse frtTokenResponse = await SilentRequestHelper.RefreshAccessTokenAsync(familyRefreshToken, _silentRequest, AuthenticationRequestParameters, cancellationToken) .ConfigureAwait(false); logger.Verbose("[FOCI] FRT refresh succeeded. "); return(frtTokenResponse); } catch (MsalServiceException ex) { // Hack: STS does not yet send back the suberror on these platforms because they are not in an allowed list, // so the best thing we can do is to consider all errors as client_mismatch. #if NETSTANDARD || WINDOWS_APP || MAC ex?.GetType(); // avoid the "variable 'ex' is declared but never used" in this code path. return(null); #else if (MsalError.InvalidGrantError.Equals(ex?.ErrorCode, StringComparison.OrdinalIgnoreCase) && MsalError.ClientMismatch.Equals(ex?.SubError, StringComparison.OrdinalIgnoreCase)) { logger.Error("[FOCI] FRT refresh failed - client mismatch. "); return(null); } // Rethrow failures to refresh the FRT, other than client_mismatch, because // apps need to handle them in the same way they handle exceptions from refreshing the RT. // For example, some apps have special handling for MFA errors. logger.Error("[FOCI] FRT refresh failed - other error. "); throw; #endif } } return(null); }
public async Task <AuthenticationResult> ExecuteAsync(CancellationToken cancellationToken) { var logger = AuthenticationRequestParameters.RequestContext.Logger; MsalAccessTokenCacheItem cachedAccessTokenItem = null; CacheRefreshReason cacheInfoTelemetry = CacheRefreshReason.NotApplicable; ThrowIfNoScopesOnB2C(); ThrowIfCurrentBrokerAccount(); AuthenticationResult authResult = null; if (!_silentParameters.ForceRefresh && string.IsNullOrEmpty(AuthenticationRequestParameters.Claims)) { cachedAccessTokenItem = await CacheManager.FindAccessTokenAsync().ConfigureAwait(false); if (cachedAccessTokenItem != null) { logger.Info("Returning access token found in cache. RefreshOn exists ? " + cachedAccessTokenItem.RefreshOn.HasValue); AuthenticationRequestParameters.RequestContext.ApiEvent.IsAccessTokenCacheHit = true; Metrics.IncrementTotalAccessTokensFromCache(); authResult = await CreateAuthenticationResultAsync(cachedAccessTokenItem).ConfigureAwait(false); } else { if (AuthenticationRequestParameters.RequestContext.ApiEvent.CacheInfo != CacheRefreshReason.Expired) { cacheInfoTelemetry = CacheRefreshReason.NoCachedAccessToken; } } } else { cacheInfoTelemetry = CacheRefreshReason.ForceRefreshOrClaims; logger.Info("Skipped looking for an Access Token because ForceRefresh or Claims were set. "); } if (AuthenticationRequestParameters.RequestContext.ApiEvent.CacheInfo == CacheRefreshReason.NotApplicable) { AuthenticationRequestParameters.RequestContext.ApiEvent.CacheInfo = cacheInfoTelemetry; } // No AT or AT neesd to be refreshed try { if (cachedAccessTokenItem == null) { authResult = await RefreshRtOrFailAsync(cancellationToken).ConfigureAwait(false); } else { var shouldRefresh = SilentRequestHelper.NeedsRefresh(cachedAccessTokenItem); // may fire a request to get a new token in the background if (shouldRefresh) { AuthenticationRequestParameters.RequestContext.ApiEvent.CacheInfo = CacheRefreshReason.ProactivelyRefreshed; SilentRequestHelper.ProcessFetchInBackground( cachedAccessTokenItem, () => RefreshRtOrFailAsync(cancellationToken), logger); } } return(authResult); } catch (MsalServiceException e) { bool isAadUnavailable = e.IsAadUnavailable(); logger.Warning($"Refreshing the RT failed. Is AAD down? {isAadUnavailable}. Is there an AT in the cache that is usable? {cachedAccessTokenItem != null} "); if (cachedAccessTokenItem != null && isAadUnavailable) { logger.Info("Returning existing access token. It is not expired, but should be refreshed. "); return(await CreateAuthenticationResultAsync(cachedAccessTokenItem).ConfigureAwait(false)); } logger.Warning("Failed to refresh the RT and cannot use existing AT (expired or missing). "); throw; } }
protected override async Task <AuthenticationResult> ExecuteAsync(CancellationToken cancellationToken) { if (AuthenticationRequestParameters.Scope == null || AuthenticationRequestParameters.Scope.Count == 0) { throw new MsalClientException( MsalError.ScopesRequired, MsalErrorMessage.ScopesRequired); } await ResolveAuthorityAsync().ConfigureAwait(false); MsalAccessTokenCacheItem cachedAccessToken = null; var logger = AuthenticationRequestParameters.RequestContext.Logger; AuthenticationResult authResult = null; CacheRefreshReason cacheInfoTelemetry = CacheRefreshReason.NotApplicable; if (!_onBehalfOfParameters.ForceRefresh && string.IsNullOrEmpty(AuthenticationRequestParameters.Claims)) { // look for access token in the cache first. // no access token is found, then it means token does not exist // or new assertion has been passed. // Look for a refresh token, if refresh token is found perform refresh token flow. // If a refresh token is not found, then it means refresh token does not exist or new assertion has been passed. // Fetch new access token for OBO using (logger.LogBlockDuration("[OBO Request] Looking in the cache for an access token")) { cachedAccessToken = await CacheManager.FindAccessTokenAsync().ConfigureAwait(false); } if (cachedAccessToken != null) { var cachedIdToken = await CacheManager.GetIdTokenCacheItemAsync(cachedAccessToken).ConfigureAwait(false); var account = await CacheManager.GetAccountAssociatedWithAccessTokenAsync(cachedAccessToken).ConfigureAwait(false); logger.Info( "[OBO Request] Found a valid access token in the cache. ID token also found? " + (cachedIdToken != null)); AuthenticationRequestParameters.RequestContext.ApiEvent.IsAccessTokenCacheHit = true; Metrics.IncrementTotalAccessTokensFromCache(); authResult = new AuthenticationResult( cachedAccessToken, cachedIdToken, AuthenticationRequestParameters.AuthenticationScheme, AuthenticationRequestParameters.RequestContext.CorrelationId, TokenSource.Cache, AuthenticationRequestParameters.RequestContext.ApiEvent, account); } else { if (AuthenticationRequestParameters.RequestContext.ApiEvent.CacheInfo != CacheRefreshReason.Expired) { cacheInfoTelemetry = CacheRefreshReason.NoCachedAccessToken; } } } else { logger.Info("[OBO Request] Skipped looking for an Access Token in the cache because ForceRefresh or Claims were set. "); cacheInfoTelemetry = CacheRefreshReason.ForceRefreshOrClaims; } if (AuthenticationRequestParameters.RequestContext.ApiEvent.CacheInfo == CacheRefreshReason.NotApplicable) { AuthenticationRequestParameters.RequestContext.ApiEvent.CacheInfo = cacheInfoTelemetry; } // No AT in the cache or AT needs to be refreshed try { if (cachedAccessToken == null) { authResult = await RefreshRtOrFetchNewAccessTokenAsync(cancellationToken).ConfigureAwait(false); } else { var shouldRefresh = SilentRequestHelper.NeedsRefresh(cachedAccessToken); // may fire a request to get a new token in the background if (shouldRefresh) { AuthenticationRequestParameters.RequestContext.ApiEvent.CacheInfo = CacheRefreshReason.ProactivelyRefreshed; SilentRequestHelper.ProcessFetchInBackground( cachedAccessToken, () => RefreshRtOrFetchNewAccessTokenAsync(cancellationToken), logger); } } return(authResult); } catch (MsalServiceException e) { return(await HandleTokenRefreshErrorAsync(e, cachedAccessToken).ConfigureAwait(false)); } }