Exemplo n.º 1
0
        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));
        }
Exemplo n.º 3
0
        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));
        }
Exemplo n.º 4
0
        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));
        }
Exemplo n.º 5
0
        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));
            }
        }
Exemplo n.º 6
0
        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);
        }
Exemplo n.º 7
0
        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));
            }
        }