예제 #1
0
 public ManageSubscriptions(IConfiguration config, IMsalAccountActivityStore msalAccountActivityStore, IServiceProvider serviceProvider, IMsalTokenCacheProvider msalTokenCacheProvider)
 {
     _config = config;
     _msalAccountActivityStore = msalAccountActivityStore;
     _serviceProvider          = serviceProvider;
     _msalTokenCacheProvider   = msalTokenCacheProvider;
 }
        /// <summary>
        /// Use a token cache and choose the serialization part by adding it to
        /// the services collection and configuring its options.
        /// </summary>
        /// <returns>The confidential client application.</returns>
        /// <param name="confidentialClientApp">Confidential client application.</param>
        /// <param name="initializeCaches">Action that you'll use to add a cache serialization
        /// to the service collection passed as an argument.</param>
        /// <returns>The application for chaining.</returns>
        /// <example>
        ///
        /// The following code adds a distributed in-memory token cache.
        ///
        /// <code>
        ///  app.AddTokenCaches(services =>
        ///  {
        ///      // In memory distributed token cache
        ///      // In net472, requires to reference Microsoft.Extensions.Caching.Memory
        ///      services.AddDistributedTokenCache();
        ///      services.AddDistributedMemoryCache();
        ///  });
        /// </code>
        ///
        /// The following code adds a token cache based on REDIS and initializes
        /// its configuration.
        ///
        /// <code>
        ///  app.AddTokenCaches(services =>
        ///  {
        ///       services.AddDistributedTokenCache();
        ///       // Redis token cache
        ///       // Requires to reference Microsoft.Extensions.Caching.StackExchangeRedis
        ///       services.AddStackExchangeRedisCache(options =>
        ///       {
        ///           options.Configuration = "localhost";
        ///           options.InstanceName = "Redis";
        ///       });
        ///  });
        /// </code>
        /// If using distributed token caches, use AddDistributedTokenCache.
        /// </example>
        /// <remarks>Don't use this method in ASP.NET Core. Just add use the ConfigureServices method
        /// instead.</remarks>
        internal static IConfidentialClientApplication AddTokenCaches(
            this IConfidentialClientApplication confidentialClientApp,
            Action <IServiceCollection> initializeCaches)
        {
            _ = confidentialClientApp ?? throw new ArgumentNullException(nameof(confidentialClientApp));
            _ = initializeCaches ?? throw new ArgumentNullException(nameof(initializeCaches));

            // try to reuse existing XYZ cache if AddXYZCache was called before, to simulate ASP.NET Core
            var serviceProvider = s_serviceProviderFromAction.GetOrAdd(initializeCaches.Method, (m) =>
            {
                lock (s_serviceProviderFromAction)
                {
                    ServiceCollection services = new ServiceCollection();
                    initializeCaches(services);
                    services.AddLogging();

                    return(services.BuildServiceProvider());
                }
            });

            IMsalTokenCacheProvider msalTokenCacheProvider = serviceProvider.GetRequiredService <IMsalTokenCacheProvider>();

            msalTokenCacheProvider.Initialize(confidentialClientApp.UserTokenCache);
            msalTokenCacheProvider.Initialize(confidentialClientApp.AppTokenCache);
            return(confidentialClientApp);
        }
예제 #3
0
파일: Connect.cs 프로젝트: pnp/contoso
 public Connect(ITokenAcquisition tokenAcquisition, GraphServiceClient graphServiceClient,
                IConfiguration config, IMsalAccountActivityStore msalAccountActivityStore, IMsalTokenCacheProvider msalTokenCacheProvider)
 {
     _tokenAcquisition         = tokenAcquisition;
     _graphServiceClient       = graphServiceClient;
     _config                   = config;
     _msalAccountActivityStore = msalAccountActivityStore;
     _msalTokenCacheProvider   = msalTokenCacheProvider;
 }
 /// <summary>
 /// Constructor of the AppServicesAuthenticationTokenAcquisition.
 /// </summary>
 /// <param name="tokenCacheProvider">The App token cache provider.</param>
 /// <param name="httpContextAccessor">Access to the HttpContext of the request.</param>
 /// <param name="httpClientFactory">HTTP client factory.</param>
 public AppServicesAuthenticationTokenAcquisition(
     IMsalTokenCacheProvider tokenCacheProvider,
     IHttpContextAccessor httpContextAccessor,
     IHttpClientFactory httpClientFactory)
 {
     _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
     _httpClientFactory   = new MsalAspNetCoreHttpClientFactory(httpClientFactory);
     _tokenCacheProvider  = tokenCacheProvider;
 }
 /// <summary>
 /// Constructor of the TokenAcquisition service. This requires the Azure AD Options to
 /// configure the confidential client application and a token cache provider.
 /// This constructor is called by ASP.NET Core dependency injection
 /// </summary>
 /// <param name="configuration"></param>
 /// <param name="tokenCacheProvider">The App token cache provider</param>
 /// <param name="userTokenCacheProvider">The User token cache provider</param>
 public TokenAcquisition(
     IMsalTokenCacheProvider tokenCacheProvider,
     IHttpContextAccessor httpContextAccessor,
     IOptions <AzureADOptions> azureAdOptions,
     IOptions <ConfidentialClientApplicationOptions> applicationOptions)
 {
     _httpContextAccessor = httpContextAccessor;
     _azureAdOptions      = azureAdOptions.Value;
     _applicationOptions  = applicationOptions.Value;
     _tokenCacheProvider  = tokenCacheProvider;
 }
 /// <summary>
 /// Constructor of the TokenAcquisition service. This requires the Azure AD Options to
 /// configure the confidential client application and a token cache provider.
 /// This constructor is called by ASP.NET Core dependency injection
 /// </summary>
 /// <param name="configuration"></param>
 /// <param name="tokenCacheProvider">The App token cache provider</param>
 /// <param name="userTokenCacheProvider">The User token cache provider</param>
 public TokenAcquisition(
     IMsalTokenCacheProvider tokenCacheProvider,
     IHttpContextAccessor httpContextAccessor,
     IOptions <MicrosoftIdentityOptions> microsoftIdentityOptions,
     IOptions <ConfidentialClientApplicationOptions> applicationOptions)
 {
     _httpContextAccessor      = httpContextAccessor;
     _microsoftIdentityOptions = microsoftIdentityOptions.Value;
     _applicationOptions       = applicationOptions.Value;
     _tokenCacheProvider       = tokenCacheProvider;
 }
예제 #7
0
        private static IMsalTokenCacheProvider CreateTokenCacheSerializer()
        {
            IServiceCollection services = new ServiceCollection();

            // In memory token cache. Other forms of serialization are possible.
            // See https://github.com/AzureAD/microsoft-identity-web/wiki/asp-net
            services.AddInMemoryTokenCaches();

            IServiceProvider        serviceProvider        = services.BuildServiceProvider();
            IMsalTokenCacheProvider msalTokenCacheProvider = serviceProvider.GetRequiredService <IMsalTokenCacheProvider>();

            return(msalTokenCacheProvider);
        }
예제 #8
0
        public static async Task <IConfidentialClientApplication> BuildConfidentialClientApplication()
        {
            IConfidentialClientApplication clientapp = ConfidentialClientApplicationBuilder.Create(AuthenticationConfig.ClientId)
                                                       .WithClientSecret(AuthenticationConfig.ClientSecret)
                                                       .WithRedirectUri(AuthenticationConfig.RedirectUri)
                                                       .WithAuthority(new Uri(AuthenticationConfig.Authority))
                                                       .Build();

            // After the ConfidentialClientApplication is created, we overwrite its default UserTokenCache serialization with our implementation
            IMsalTokenCacheProvider memoryTokenCacheProvider = CreateTokenCacheSerializer();
            await memoryTokenCacheProvider.InitializeAsync(clientapp.UserTokenCache);

            return(clientapp);
        }
 /// <summary>
 /// Constructor of the TokenAcquisition service. This requires the Azure AD Options to
 /// configure the confidential client application and a token cache provider.
 /// This constructor is called by ASP.NET Core dependency injection.
 /// </summary>
 /// <param name="tokenCacheProvider">The App token cache provider.</param>
 /// <param name="httpContextAccessor">Access to the HttpContext of the request.</param>
 /// <param name="microsoftIdentityOptions">Configuration options.</param>
 /// <param name="applicationOptions">MSAL.NET configuration options.</param>
 /// <param name="httpClientFactory">HTTP client factory.</param>
 /// <param name="logger">Logger.</param>
 public TokenAcquisition(
     IMsalTokenCacheProvider tokenCacheProvider,
     IHttpContextAccessor httpContextAccessor,
     IOptions <MicrosoftIdentityOptions> microsoftIdentityOptions,
     IOptions <ConfidentialClientApplicationOptions> applicationOptions,
     IHttpClientFactory httpClientFactory,
     ILogger <TokenAcquisition> logger)
 {
     _httpContextAccessor      = httpContextAccessor;
     _microsoftIdentityOptions = microsoftIdentityOptions.Value;
     _applicationOptions       = applicationOptions.Value;
     _tokenCacheProvider       = tokenCacheProvider;
     _httpClientFactory        = new MsalAspNetCoreHttpClientFactory(httpClientFactory);
     _logger = logger;
 }
예제 #10
0
        public static async Task RemoveAccount()
        {
            IConfidentialClientApplication clientapp = ConfidentialClientApplicationBuilder.Create(AuthenticationConfig.ClientId)
                                                       .WithClientSecret(AuthenticationConfig.ClientSecret)
                                                       .WithRedirectUri(AuthenticationConfig.RedirectUri)
                                                       .WithAuthority(new Uri(AuthenticationConfig.Authority))
                                                       .Build();

            // We only clear the user's tokens.
            IMsalTokenCacheProvider memoryTokenCacheProvider = CreateTokenCacheSerializer();
            await memoryTokenCacheProvider.InitializeAsync(clientapp.UserTokenCache);

            var userAccount = await clientapp.GetAccountAsync(ClaimsPrincipal.Current.GetAccountId());

            if (userAccount != null)
            {
                await clientapp.RemoveAsync(userAccount);
            }
        }
        /// <summary>
        /// Constructor of the TokenAcquisition service. This requires the Azure AD Options to
        /// configure the confidential client application and a token cache provider.
        /// This constructor is called by ASP.NET Core dependency injection.
        /// </summary>
        /// <param name="tokenCacheProvider">The App token cache provider.</param>
        /// <param name="httpContextAccessor">Access to the HttpContext of the request.</param>
        /// <param name="microsoftIdentityOptions">Configuration options.</param>
        /// <param name="applicationOptions">MSAL.NET configuration options.</param>
        /// <param name="httpClientFactory">HTTP client factory.</param>
        /// <param name="logger">Logger.</param>
        /// <param name="serviceProvider">Service provider.</param>
        public TokenAcquisition(
            IMsalTokenCacheProvider tokenCacheProvider,
            IHttpContextAccessor httpContextAccessor,
            IOptions <MicrosoftIdentityOptions> microsoftIdentityOptions,
            IOptions <ConfidentialClientApplicationOptions> applicationOptions,
            IHttpClientFactory httpClientFactory,
            ILogger <TokenAcquisition> logger,
            IServiceProvider serviceProvider)
        {
            _httpContextAccessor      = httpContextAccessor;
            _microsoftIdentityOptions = microsoftIdentityOptions.Value;
            _applicationOptions       = applicationOptions.Value;
            _tokenCacheProvider       = tokenCacheProvider;
            _httpClientFactory        = new MsalAspNetCoreHttpClientFactory(httpClientFactory);
            _logger          = logger;
            _serviceProvider = serviceProvider;

            _applicationOptions.ClientId ??= _microsoftIdentityOptions.ClientId;
            _applicationOptions.Instance ??= _microsoftIdentityOptions.Instance;
            _applicationOptions.ClientSecret ??= _microsoftIdentityOptions.ClientSecret;
            _applicationOptions.TenantId ??= _microsoftIdentityOptions.TenantId;
        }
        /// <summary>
        /// Constructor of the TokenAcquisition service. This requires the Azure AD Options to
        /// configure the confidential client application and a token cache provider.
        /// This constructor is called by ASP.NET Core dependency injection.
        /// </summary>
        /// <param name="tokenCacheProvider">The App token cache provider.</param>
        /// <param name="httpContextAccessor">Access to the HttpContext of the request.</param>
        /// <param name="microsoftIdentityOptions">Configuration options.</param>
        /// <param name="applicationOptions">MSAL.NET configuration options.</param>
        /// <param name="httpClientFactory">HTTP client factory.</param>
        /// <param name="logger">Logger.</param>
        /// <param name="serviceProvider">Service provider.</param>
        public TokenAcquisition(
            IMsalTokenCacheProvider tokenCacheProvider,
            IHttpContextAccessor httpContextAccessor,
            IOptions <MicrosoftIdentityOptions> microsoftIdentityOptions,
            IOptions <ConfidentialClientApplicationOptions> applicationOptions,
            IHttpClientFactory httpClientFactory,
            ILogger <TokenAcquisition> logger,
            IServiceProvider serviceProvider)
        {
            _httpContextAccessor      = httpContextAccessor;
            _microsoftIdentityOptions = microsoftIdentityOptions.Value;
            _applicationOptions       = applicationOptions.Value;
            _tokenCacheProvider       = tokenCacheProvider;
            _httpClientFactory        = new MsalAspNetCoreHttpClientFactory(httpClientFactory);
            _logger          = logger;
            _serviceProvider = serviceProvider;

            _applicationOptions.ClientId ??= _microsoftIdentityOptions.ClientId;
            _applicationOptions.Instance ??= _microsoftIdentityOptions.Instance;
            _applicationOptions.ClientSecret ??= _microsoftIdentityOptions.ClientSecret;
            _applicationOptions.TenantId ??= _microsoftIdentityOptions.TenantId;
            _applicationOptions.LegacyCacheCompatibilityEnabled          = _microsoftIdentityOptions.LegacyCacheCompatibilityEnabled;
            DefaultCertificateLoader.UserAssignedManagedIdentityClientId = _microsoftIdentityOptions.UserAssignedManagedIdentityClientId;
        }
예제 #13
0
        static async Task Main(string[] args)
        {
            string clientId     = "812287fd-3ea3-49c6-b4ab-e8d41dea1f17";
            string clientSecret = "[Enter here the secret register with your application]";
            string tenant       = "msidentitysamplestesting.onmicrosoft.com";

            string[] scopes = new[] { "api://2d96f90e-a1a7-4ef5-b15c-87758986eb1a/.default" };

            CacheImplementationDemo cacheImplementation = CacheImplementationDemo.InMemory;

            // Create the token cache (4 possible implementations)
            IMsalTokenCacheProvider msalTokenCacheProvider = CreateTokenCache(cacheImplementation);

            // Create the confidential client application
            IConfidentialClientApplication app;

            app = ConfidentialClientApplicationBuilder.Create(clientId)
                  .WithClientSecret(clientSecret)
                  .WithTenantId(tenant)
                  .Build();

            await msalTokenCacheProvider.InitializeAsync(app.UserTokenCache);

            await msalTokenCacheProvider.InitializeAsync(app.AppTokenCache);

            // Acquire a token (twice)
            var result = await app.AcquireTokenForClient(scopes)
                         .ExecuteAsync();

            Console.WriteLine(result.AuthenticationResultMetadata.TokenSource);

            result = await app.AcquireTokenForClient(scopes)
                     .ExecuteAsync();

            Console.WriteLine(result.AuthenticationResultMetadata.TokenSource);
        }
        public void CreateTokenCacheSerializerTest()
        {
            IMsalTokenCacheProvider tokenCacheProvider = CreateTokenCacheSerializer();

            Assert.NotNull(tokenCacheProvider);
        }
예제 #15
0
        /// <summary>
        /// Creates a token cache (implementation of your choice)
        /// </summary>
        /// <param name="cacheImplementation">implementation for the token cache</param>
        /// <returns>An Msal Token cache provider</returns>
        private static IMsalTokenCacheProvider CreateTokenCache(CacheImplementationDemo cacheImplementation = CacheImplementationDemo.InMemory)
        {
            IServiceCollection services = new ServiceCollection();

            // (Simulates the configuration, could be a IConfiguration or anything)
            Dictionary <string, string> Configuration = new Dictionary <string, string>();

            switch (cacheImplementation)
            {
            case CacheImplementationDemo.InMemory:
                // In memory token cache
                // In net472, requires to reference Microsoft.Extensions.Caching.Memory
                services.AddInMemoryTokenCaches();
                break;

            case CacheImplementationDemo.DistributedMemory:
                // In memory distributed token cache
                // In net472, requires to reference Microsoft.Extensions.Caching.Memory
                services.AddDistributedTokenCaches();
                services.AddDistributedMemoryCache();
                break;

            case CacheImplementationDemo.DistributedSqlServer:
                // SQL Server token cache
                // Requires to reference Microsoft.Extensions.Caching.SqlServer
                services.AddDistributedTokenCaches();
                services.AddDistributedSqlServerCache(options =>
                {
                    options.ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=TestCache;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
                    options.SchemaName       = "dbo";
                    options.TableName        = "TestCache";

                    // You don't want the SQL token cache to be purged before the access token has expired. Usually
                    // access tokens expire after 1 hour (but this can be changed by token lifetime policies), whereas
                    // the default sliding expiration for the distributed SQL database is 20 mins.
                    // Use a value which is above 60 mins (or the lifetime of a token in case of longer lived tokens)
                    options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
                });
                break;

            case CacheImplementationDemo.StackExchangeRedis:
                // Redis token cache
                // Requires to reference Microsoft.Extensions.Caching.StackExchangeRedis
                services.AddDistributedTokenCaches();
                services.AddStackExchangeRedisCache(options =>
                {
                    options.Configuration = "localhost";
                    options.InstanceName  = "SampleInstance";
                });
                break;

            case CacheImplementationDemo.CosmosDb:
                // Redis token cache
                // Requires to reference Microsoft.Extensions.Caching.Cosmos (preview)
                services.AddDistributedTokenCaches();
                services.AddCosmosCache((CosmosCacheOptions cacheOptions) =>
                {
                    cacheOptions.ContainerName     = Configuration["CosmosCacheContainer"];
                    cacheOptions.DatabaseName      = Configuration["CosmosCacheDatabase"];
                    cacheOptions.ClientBuilder     = new CosmosClientBuilder(Configuration["CosmosConnectionString"]);
                    cacheOptions.CreateIfNotExists = true;
                });
                break;

            default:
                break;
            }

            IServiceProvider        serviceProvider        = services.BuildServiceProvider();
            IMsalTokenCacheProvider msalTokenCacheProvider = serviceProvider.GetRequiredService <IMsalTokenCacheProvider>();

            return(msalTokenCacheProvider);
        }
예제 #16
0
        /// <summary>
        /// Creates a token cache (implementation of your choice)
        /// </summary>
        /// <param name="cacheImplementation">implementation for the token cache</param>
        /// <returns>An Msal Token cache provider</returns>
        private static IMsalTokenCacheProvider CreateTokenCache(CacheImplementationDemo cacheImplementation = CacheImplementationDemo.InMemory)
        {
            IServiceCollection services = new ServiceCollection();

            // (Simulates the configuration, could be a IConfiguration or anything)
            Dictionary <string, string> Configuration = new Dictionary <string, string>();

            switch (cacheImplementation)
            {
            case CacheImplementationDemo.InMemory:
                // In memory token cache
                // In net472, requires to reference Microsoft.Extensions.Caching.Memory
                services.AddInMemoryTokenCaches();
                break;

            case CacheImplementationDemo.DistributedMemory:
                // In memory distributed token cache
                // In net472, requires to reference Microsoft.Extensions.Caching.Memory
                services.AddDistributedTokenCaches();
                services.AddDistributedMemoryCache();
                break;

            case CacheImplementationDemo.DistributedSqlServer:
                // SQL Server token cache
                // Requires to reference Microsoft.Extensions.Caching.SqlServer
                services.AddDistributedTokenCaches();
                services.AddDistributedSqlServerCache(options =>
                {
                    options.ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=TestCache;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
                    options.SchemaName       = "dbo";
                    options.TableName        = "TestCache";
                });
                break;

            case CacheImplementationDemo.StackExchangeRedis:
                // Redis token cache
                // Requires to reference Microsoft.Extensions.Caching.StackExchangeRedis
                services.AddDistributedTokenCaches();
                services.AddStackExchangeRedisCache(options =>
                {
                    options.Configuration = "localhost";
                    options.InstanceName  = "SampleInstance";
                });
                break;

            case CacheImplementationDemo.CosmosDb:
                // Redis token cache
                // Requires to reference Microsoft.Extensions.Caching.Cosmos (preview)
                services.AddCosmosCache((CosmosCacheOptions cacheOptions) =>
                {
                    cacheOptions.ContainerName     = Configuration["CosmosCacheContainer"];
                    cacheOptions.DatabaseName      = Configuration["CosmosCacheDatabase"];
                    cacheOptions.ClientBuilder     = new CosmosClientBuilder(Configuration["CosmosConnectionString"]);
                    cacheOptions.CreateIfNotExists = true;
                });
                break;

            default:
                break;
            }

            IServiceProvider        serviceProvider        = services.BuildServiceProvider();
            IMsalTokenCacheProvider msalTokenCacheProvider = serviceProvider.GetRequiredService <IMsalTokenCacheProvider>();

            return(msalTokenCacheProvider);
        }
예제 #17
0
        internal static async Task ManageSubscription(SubscriptionActivity currentSubscriptionActivity, string oid, string tid, string upn, IConfiguration config, IMsalAccountActivityStore msalAccountActivityStore, GraphServiceClient _graphServiceClient, IMsalTokenCacheProvider msalTokenCacheProvider)
        {
            string subscriptionId  = null;
            string changeToken     = null;
            string notiticationUrl = config.GetValue <string>("Files:SubscriptionService");
            int    subscriptionLifeTimeInMinutes = config.GetValue <int>("Files:SubscriptionLifeTime");

            if (subscriptionLifeTimeInMinutes == 0)
            {
                subscriptionLifeTimeInMinutes = 15;
            }

            // Load the current subscription (if any)
            var currentSubscriptions = await _graphServiceClient.Subscriptions.Request().GetAsync();

            var currentOneDriveSubscription = currentSubscriptions.FirstOrDefault(p => p.Resource == "me/drive/root");

            // If present and still using the same subscription host then update the subscription expiration date
            if (currentOneDriveSubscription != null && currentOneDriveSubscription.NotificationUrl.Equals(notiticationUrl, StringComparison.InvariantCultureIgnoreCase))
            {
                // Extend the expiration of the current subscription
                subscriptionId = currentOneDriveSubscription.Id;
                currentOneDriveSubscription.ExpirationDateTime = DateTimeOffset.UtcNow.AddMinutes(subscriptionLifeTimeInMinutes);
                currentOneDriveSubscription.ClientState        = Constants.FilesSubscriptionServiceClientState;
                await _graphServiceClient.Subscriptions[currentOneDriveSubscription.Id].Request().UpdateAsync(currentOneDriveSubscription);

                // Check if the last change token was populated
                if (currentSubscriptionActivity == null)
                {
                    currentSubscriptionActivity = await msalAccountActivityStore.GetSubscriptionActivityForUserSubscription(oid, tid, upn, subscriptionId);
                }

                if (currentSubscriptionActivity != null)
                {
                    changeToken = currentSubscriptionActivity.LastChangeToken;
                }
            }
            else
            {
                // Add a new subscription
                var newSubscription = await _graphServiceClient.Subscriptions.Request().AddAsync(new Subscription()
                {
                    ChangeType                = "updated",
                    NotificationUrl           = notiticationUrl,
                    Resource                  = "me/drive/root",
                    ExpirationDateTime        = DateTimeOffset.UtcNow.AddMinutes(subscriptionLifeTimeInMinutes),
                    ClientState               = Constants.FilesSubscriptionServiceClientState,
                    LatestSupportedTlsVersion = "v1_2"
                });

                subscriptionId = newSubscription.Id;
            }

            // Store the user principal name with the subscriptionid as that's the mechanism needed to connect change event with tenant/user
            var cacheEntriesToRemove = await msalAccountActivityStore.UpdateSubscriptionId(subscriptionId, oid, tid, upn);

            // If we've found old MSAL cache entries for which we've removed the respective MsalActivity records we should also
            // drop these from the MSAL cache itself
            if (cacheEntriesToRemove.Any())
            {
                foreach (var cacheEntry in cacheEntriesToRemove)
                {
                    await(msalTokenCacheProvider as IntegratedTokenCacheAdapter).RemoveKeyFromCache(cacheEntry);
                }
            }

            if (changeToken == null)
            {
                // Initialize the subscription and get the latest change token, to avoid getting back all the historical changes
                IDriveItemDeltaCollectionPage deltaCollection = await _graphServiceClient.Me.Drive.Root.Delta("latest").Request().GetAsync();

                var deltaLink = deltaCollection.AdditionalData["@odata.deltaLink"];
                if (!string.IsNullOrEmpty(deltaLink.ToString()))
                {
                    changeToken = ProcessChanges.GetChangeTokenFromUrl(deltaLink.ToString());
                }
            }

            // Store a record per user/subscription to track future delta queries
            if (currentSubscriptionActivity == null)
            {
                currentSubscriptionActivity = new SubscriptionActivity(oid, tid, upn)
                {
                    SubscriptionId  = subscriptionId,
                    LastChangeToken = changeToken
                };
            }

            currentSubscriptionActivity.LastChangeToken = changeToken;
            currentSubscriptionActivity.SubscriptionId  = subscriptionId;

            await msalAccountActivityStore.UpsertSubscriptionActivity(currentSubscriptionActivity);
        }