public void TestInitialize()
        {
            var storageBuilder = new StorageCreationPropertiesBuilder(
                Path.GetFileName(CacheFilePath),
                Path.GetDirectoryName(CacheFilePath));

            storageBuilder = storageBuilder.WithMacKeyChain(
                serviceName: "Microsoft.Developer.IdentityService.Test",
                accountName: "MSALCacheTest");

            // unit tests run on Linux boxes without LibSecret
            storageBuilder.WithLinuxUnprotectedFile();

            // 1. Use MSAL to create an instance of the Public Client Application
            var app = PublicClientApplicationBuilder.Create(ClientId)
                      .Build();

            // 3. Create the high level MsalCacheHelper based on properties and a logger
            _cacheHelper = MsalCacheHelper.CreateAsync(
                storageBuilder.Build(),
                new TraceSource("MSAL.CacheExtension.Test"))
                           .GetAwaiter().GetResult();

            // 4. Let the cache helper handle MSAL's cache
            _cacheHelper.RegisterCache(app.UserTokenCache);
        }
예제 #2
0
        public void UnprotectedOptionMutuallyExclusiveWithOtherOptions()
        {
            var builder = new StorageCreationPropertiesBuilder(
                Path.GetFileName(CacheFilePath),
                Path.GetDirectoryName(CacheFilePath));

            builder = builder.WithMacKeyChain(serviceName: "Microsoft.Developer.IdentityService", accountName: "MSALCache");
            builder.WithUnprotectedFile();

            AssertException.Throws <ArgumentException>(() => builder.Build());


            builder = new StorageCreationPropertiesBuilder(
                Path.GetFileName(CacheFilePath),
                Path.GetDirectoryName(CacheFilePath));

            builder = builder.WithLinuxKeyring(
                schemaName: "msal.cache",
                collection: "default",
                secretLabel: "MSALCache",
                attribute1: new KeyValuePair <string, string>("MsalClientID", "Microsoft.Developer.IdentityService"),
                attribute2: new KeyValuePair <string, string>("MsalClientVersion", "1.0.0.0"));
            builder.WithUnprotectedFile();
            AssertException.Throws <ArgumentException>(() => builder.Build());

            builder = new StorageCreationPropertiesBuilder(
                Path.GetFileName(CacheFilePath),
                Path.GetDirectoryName(CacheFilePath));
            builder.WithLinuxUnprotectedFile();
            builder.WithUnprotectedFile();

            AssertException.Throws <ArgumentException>(() => builder.Build());
        }
        /// <inheritdoc />
        public SharedTokenCacheProvider(IConfiguration config = null, ILogger logger = null)
        {
            _logger = logger;
            _config = config ?? new ConfigurationBuilder().AddEnvironmentVariables().Build();

            const string serviceName = "Microsoft.Developer.IdentityService";
            const string clientId    = "04b07795-8ddb-461a-bbee-02f9e1bf7b46";
            var          storageCreationPropertiesBuilder = new StorageCreationPropertiesBuilder(
                Path.GetFileName(s_cacheFilePath),
                Path.GetDirectoryName(s_cacheFilePath),
                clientId)
                                                            .WithMacKeyChain(serviceName: serviceName, accountName: "MSALCache")
                                                            .WithLinuxKeyring(
                schemaName: "msal.cache",
                collection: "default",
                secretLabel: "MSALCache",
                attribute1: new KeyValuePair <string, string>("MsalClientID", serviceName),
                attribute2: new KeyValuePair <string, string>("MsalClientVersion", "1.0.0.0"));

            var authority = string.Format(CultureInfo.InvariantCulture,
                                          AadAuthority.AadCanonicalAuthorityTemplate,
                                          AadAuthority.DefaultTrustedHost,
                                          "common");

            _app = PublicClientApplicationBuilder
                   .Create(clientId)
                   .WithAuthority(new Uri(authority))
                   .Build();

            var cacheStore = new MsalCacheStorage(storageCreationPropertiesBuilder.Build());

            _cacheHelper = new MsalCacheHelper(_app.UserTokenCache, cacheStore);
            _cacheHelper.RegisterCache(_app.UserTokenCache);
        }
        public static void ClassInitialize(TestContext _)
        {
            var builder = new StorageCreationPropertiesBuilder(Path.GetFileName(CacheFilePath), Path.GetDirectoryName(CacheFilePath), "ClientIDGoesHere");

            builder = builder.WithMacKeyChain(serviceName: "Microsoft.Developer.IdentityService", accountName: "MSALCache");

            // Tests run on machines without Libsecret
            builder = builder.WithLinuxUnprotectedFile();
            s_storageCreationProperties = builder.Build();
        }
 public static void ClassInitialize(TestContext _)
 {
     var builder = new StorageCreationPropertiesBuilder(Path.GetFileName(CacheFilePath), Path.GetDirectoryName(CacheFilePath), "ClientIDGoesHere");
     builder = builder.WithMacKeyChain(serviceName: "Microsoft.Developer.IdentityService", accountName: "MSALCache");
     builder = builder.WithLinuxKeyring(
         schemaName: "msal.cache",
         collection: "default",
         secretLabel: "MSALCache",
         attribute1: new KeyValuePair<string, string>("MsalClientID", "Microsoft.Developer.IdentityService"),
         attribute2: new KeyValuePair<string, string>("MsalClientVersion", "1.0.0.0"));
     s_storageCreationProperties = builder.Build();
 }
예제 #6
0
        /// <summary>
        /// Gets an aptly configured instance of the <see cref="MsalCacheStorage" /> class.
        /// </summary>
        /// <returns>An aptly configured instance of the <see cref="MsalCacheStorage" /> class.</returns>
        private MsalCacheHelper GetMsalCacheStorage()
        {
            StorageCreationPropertiesBuilder builder = new StorageCreationPropertiesBuilder(Path.GetFileName(CacheFilePath), Path.GetDirectoryName(CacheFilePath), ClientId);

            builder = builder.WithMacKeyChain(serviceName: "Microsoft.Developer.IdentityService", accountName: "MSALCache");
            builder = builder.WithLinuxKeyring(
                schemaName: "msal.cache",
                collection: "default",
                secretLabel: "MSALCache",
                attribute1: new KeyValuePair <string, string>("MsalClientID", "Microsoft.Developer.IdentityService"),
                attribute2: new KeyValuePair <string, string>("MsalClientVersion", "1.0.0.0"));

            return(MsalCacheHelper.CreateAsync(builder.Build(), new TraceSource("Partner Center PowerShell")).ConfigureAwait(false).GetAwaiter().GetResult());
        }
        /// <summary>
        /// Gets an aptly configured instance of the <see cref="MsalCacheStorage" /> class.
        /// </summary>
        /// <returns>An aptly configured instance of the <see cref="MsalCacheStorage" /> class.</returns>
        private MsalCacheStorage GetMsalCacheStorage()
        {
            StorageCreationPropertiesBuilder builder = new StorageCreationPropertiesBuilder(Path.GetFileName(CacheFilePath), Path.GetDirectoryName(CacheFilePath), ClientId);

            builder = builder.WithMacKeyChain(serviceName: "Microsoft.Developer.IdentityService", accountName: "MSALCache");
            builder = builder.WithLinuxKeyring(
                schemaName: "msal.cache",
                collection: "default",
                secretLabel: "MSALCache",
                attribute1: new KeyValuePair <string, string>("MsalClientID", "Microsoft.Developer.IdentityService"),
                attribute2: new KeyValuePair <string, string>("MsalClientVersion", "1.0.0.0"));

            return(new MsalCacheStorage(builder.Build()));
        }
        public void ImportExport()
        {
            // Arrange
            var cacheAccessor = NSubstitute.Substitute.For <ICacheAccessor>();
            var cache         = new MockTokenCache();
            var storage       = new Storage(
                _storageCreationPropertiesBuilder.Build(),
                cacheAccessor,
                new TraceSourceLogger(new TraceSource("ts")));
            var helper = new MsalCacheHelper(cache, storage, _logger);

            byte[] dataToSave = Encoding.UTF8.GetBytes("Hello World 2");

            cacheAccessor.Read().Returns(Encoding.UTF8.GetBytes("Hello World"));

            // Act
            byte[] actualData = helper.LoadUnencryptedTokenCache();
            helper.SaveUnencryptedTokenCache(dataToSave);

            // Assert
            Assert.AreEqual("Hello World", Encoding.UTF8.GetString(actualData));
            cacheAccessor.Received().Write(dataToSave);
        }
예제 #9
0
        private async Task <MsalCacheHelper> GetMsalCacheHelperAsync()
        {
            // There are options to set up the cache correctly using StorageCreationProperties on other OS's but that will need to be tested
            // for now only support windows
            if (helper == null && this.cacheEnabled && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                var fileName  = Path.GetFileName(cacheLocation);
                var directory = Path.GetDirectoryName(cacheLocation);

                var builder = new StorageCreationPropertiesBuilder(fileName, directory, this.clientId);
                StorageCreationProperties creationProps = builder.Build();
                helper = await MsalCacheHelper.CreateAsync(creationProps);
            }

            return(helper);
        }
예제 #10
0
        private static MsalCacheHelper InitializeCacheHelper(string clientId)
        {
            StorageCreationPropertiesBuilder builder = new StorageCreationPropertiesBuilder(Path.GetFileName(CacheFilePath), Path.GetDirectoryName(CacheFilePath), clientId);

            builder = builder.WithMacKeyChain(serviceName: "Microsoft.Developer.IdentityService", accountName: "MSALCache");
            builder = builder.WithLinuxKeyring(
                schemaName: "msal.cache",
                collection: "default",
                secretLabel: "MSALCache",
                attribute1: new KeyValuePair <string, string>("MsalClientID", "Microsoft.Developer.IdentityService"),
                attribute2: new KeyValuePair <string, string>("MsalClientVersion", "1.0.0.0"));

            StorageCreationProperties storageCreationProperties = builder.Build();

            return(MsalCacheHelper.CreateAsync(storageCreationProperties).ConfigureAwait(false).GetAwaiter().GetResult());
        }
예제 #11
0
        internal static (string[], IPublicClientApplication, MsalCacheHelper) GetPublicClient(
            string resource,
            string tenant,
            Uri baseAuthority,
            bool validateAuthority,
            string clientId,
            string cacheFilename,
            string cacheDirectory,
            string serviceName,
            string accountName)
        {
            // tenant can be null
            resource = resource ?? throw new ArgumentNullException(nameof(resource));

            Console.WriteLine($"Using resource: '{resource}', tenant:'{tenant}'");

            var scopes = new string[] { resource + "/.default" };

            Console.WriteLine($"Using scopes: '{string.Join(",", scopes)}'");

            var authority = $"{baseAuthority.AbsoluteUri}{tenant}";

            Console.WriteLine($"GetPublicClient for authority: '{authority}' ValidateAuthority: '{validateAuthority}'");

            Uri authorityUri = new Uri(authority);
            var appBuilder   = PublicClientApplicationBuilder.Create(clientId).WithAuthority(authorityUri, validateAuthority);
            var app          = appBuilder.Build();

            Console.WriteLine($"Built public client");

            var storageCreationPropsBuilder = new StorageCreationPropertiesBuilder(cacheFilename, cacheDirectory);

            storageCreationPropsBuilder = storageCreationPropsBuilder.WithMacKeyChain(serviceName, accountName);
            var storageCreationProps = storageCreationPropsBuilder.Build();

            // This hooks up our custom cache onto the one used by MSAL
            var cacheHelper = new MsalCacheHelper(storageCreationProps);

            cacheHelper.RegisterCache(app.UserTokenCache);

            Console.WriteLine($"Cache registered");

            return(scopes, app, cacheHelper);
        }
예제 #12
0
        internal SharedTokenCacheProvider(StorageCreationPropertiesBuilder builder, IConfiguration config = null, ILogger logger = null)
        {
            _logger = logger;
            _config = config ?? new ConfigurationBuilder().AddEnvironmentVariables().Build();

            var authority = _config.GetValue <string>(Constants.AadAuthorityEnvName) ??
                            string.Format(CultureInfo.InvariantCulture,
                                          AadAuthority.AadCanonicalAuthorityTemplate,
                                          AadAuthority.DefaultTrustedHost,
                                          "common");

            _app = PublicClientApplicationBuilder
                   .Create(AzureCliClientId)
                   .WithAuthority(new Uri(authority))
                   .Build();

            var cacheStore = new MsalCacheStorage(builder.Build());

            _cacheHelper = new MsalCacheHelper(_app.UserTokenCache, cacheStore);
            _cacheHelper.RegisterCache(_app.UserTokenCache);
        }
예제 #13
0
        public void ImportExport()
        {
            var storageBuilder = new StorageCreationPropertiesBuilder(
                Path.GetFileName(CacheFilePath),
                Path.GetDirectoryName(CacheFilePath),
                ClientId);

            storageBuilder = storageBuilder.WithMacKeyChain(
                serviceName: "Microsoft.Developer.IdentityService.Test",
                accountName: "MSALCacheTest");

            // unit tests run on Linux boxes without LibSecret
            storageBuilder.WithLinuxUnprotectedFile();

            // 1. Use MSAL to create an instance of the Public Client Application
            var app = PublicClientApplicationBuilder.Create(ClientId)
                      .Build();

            // 3. Create the high level MsalCacheHelper based on properties and a logger
            _cacheHelper = MsalCacheHelper.CreateAsync(
                storageBuilder.Build(),
                new TraceSource("MSAL.CacheExtension.Test"))
                           .GetAwaiter().GetResult();

            // 4. Let the cache helper handle MSAL's cache
            _cacheHelper.RegisterCache(app.UserTokenCache);

            // Act
            string dataString = "Hello World";

            byte[] dataBytes = Encoding.UTF8.GetBytes(dataString);
            var    result    = _cacheHelper.LoadUnencryptedTokenCache();

            Assert.AreEqual(0, result.Length);

            _cacheHelper.SaveUnencryptedTokenCache(dataBytes);
            byte[] actualData = _cacheHelper.LoadUnencryptedTokenCache();

            Assert.AreEqual(dataString, Encoding.UTF8.GetString(actualData));
        }
예제 #14
0
        private StorageCreationProperties CreateTokenCacheProps(string clientId, bool useLinuxFallback)
        {
            const string cacheFileName = "msal.cache";
            string       cacheDirectory;

            if (PlatformUtils.IsWindows())
            {
                // The shared MSAL cache is located at "%LocalAppData%\.IdentityService\msal.cache" on Windows.
                cacheDirectory = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                    ".IdentityService"
                    );
            }
            else
            {
                // The shared MSAL cache metadata is located at "~/.local/.IdentityService/msal.cache" on UNIX.
                cacheDirectory = Path.Combine(Context.FileSystem.UserHomePath, ".local", ".IdentityService");
            }

            // The keychain is used on macOS with the following service & account names
            var builder = new StorageCreationPropertiesBuilder(cacheFileName, cacheDirectory, clientId)
                          .WithMacKeyChain("Microsoft.Developer.IdentityService", "MSALCache");

            if (useLinuxFallback)
            {
                builder.WithLinuxUnprotectedFile();
            }
            else
            {
                // The SecretService/keyring is used on Linux with the following collection name and attributes
                builder.WithLinuxKeyring(cacheFileName,
                                         "default", "MSALCache",
                                         new KeyValuePair <string, string>("MsalClientID", "Microsoft.Developer.IdentityService"),
                                         new KeyValuePair <string, string>("Microsoft.Developer.IdentityService", "1.0.0.0"));
            }

            return(builder.Build());
        }
예제 #15
0
        private static void StoreOtherSecret()
        {
            var storageProperties = new StorageCreationPropertiesBuilder(
                Config.CacheFileName + ".other_secrets",
                Config.CacheDir)
                                    .WithMacKeyChain(
                Config.KeyChainServiceName + ".other_secrets",
                Config.KeyChainAccountName)
                                    .WithLinuxKeyring(
                Config.LinuxKeyRingSchema,
                Config.LinuxKeyRingCollection,
                Config.LinuxKeyRingLabel,
                Config.LinuxKeyRingAttr1,
                new KeyValuePair <string, string>("other_secrets", "secret_description"));

            Storage storage = Storage.Create(storageProperties.Build());

            byte[] secretBytes = Encoding.UTF8.GetBytes("secret");

            using (new CrossPlatLock(Config.CacheFileName + ".other_secrets.lock"))
            {
                Console.WriteLine("Writing...");
                storage.WriteData(secretBytes);

                Console.WriteLine("Writing again...");
                storage.WriteData(secretBytes);


                Console.WriteLine("Reading...");
                var data = storage.ReadData();
                Console.WriteLine("Read: " + Encoding.UTF8.GetString(data));

                Console.WriteLine("Deleting...");
                storage.Clear();
            }
        }
예제 #16
0
        public void MultiAccessSerializationAsync()
        {
            var cache1  = new MockTokenCache();
            var helper1 = new MsalCacheHelper(
                cache1,
                new MsalCacheStorage(_storageCreationPropertiesBuilder.Build(), _logger),
                _logger);

            var cache2  = new MockTokenCache();
            var helper2 = new MsalCacheHelper(
                cache2,
                new MsalCacheStorage(_storageCreationPropertiesBuilder.Build(), _logger),
                _logger);

            //Test signalling thread 1
            var resetEvent1 = new ManualResetEventSlim(initialState: false);

            //Test signalling thread 2
            var resetEvent2 = new ManualResetEventSlim(initialState: false);

            //Thread 1 signalling test
            var resetEvent3 = new ManualResetEventSlim(initialState: false);

            // Thread 2 signalling test
            var resetEvent4 = new ManualResetEventSlim(initialState: false);

            var thread1 = new Thread(() =>
            {
                var args = new TokenCacheNotificationArgs(cache1, string.Empty, null, false);

                helper1.BeforeAccessNotification(args);
                resetEvent3.Set();
                resetEvent1.Wait();
                helper1.AfterAccessNotification(args);
            });

            var thread2 = new Thread(() =>
            {
                var args = new TokenCacheNotificationArgs(cache2, string.Empty, null, false);
                helper2.BeforeAccessNotification(args);
                resetEvent4.Set();
                resetEvent2.Wait();
                helper2.AfterAccessNotification(args);
                resetEvent4.Set();
            });

            // Let thread 1 start and get the lock
            thread1.Start();
            resetEvent3.Wait();

            // Start thread 2 and give it enough time to get blocked on the lock
            thread2.Start();
            Thread.Sleep(5000);

            // Make sure helper1 has the lock still, and helper2 doesn't
            Assert.IsNotNull(helper1.CacheLock);
            Assert.IsNull(helper2.CacheLock);

            // Allow thread1 to give up the lock, and wait for helper2 to get it
            resetEvent1.Set();
            resetEvent4.Wait();
            resetEvent4.Reset();

            // Make sure helper1 gave it up properly, and helper2 now owns the lock
            Assert.IsNull(helper1.CacheLock);
            Assert.IsNotNull(helper2.CacheLock);

            // Allow thread2 to give up the lock, and wait for it to complete
            resetEvent2.Set();
            resetEvent4.Wait();

            // Make sure thread2 cleaned up after itself as well
            Assert.IsNull(helper2.CacheLock);
        }
#pragma warning disable UseAsyncSuffix // Use Async suffix
        static async Task Main(string[] args)
#pragma warning restore UseAsyncSuffix // Use Async suffix
        {
            // It's recommended to create a separate PublicClient Application for each tenant
            // but only one CacheHelper object
            var pca         = CreatePublicClient("https://login.microsoftonline.com/organizations");
            var cacheHelper = await CreateCacheHelperAsync().ConfigureAwait(false);

            cacheHelper.RegisterCache(pca.UserTokenCache);

            // Advanced scenario for when 2 or more apps share the same cache
            cacheHelper.CacheChanged += (s, e) => // this event is very expensive perf wise
            {
                Console.BackgroundColor = ConsoleColor.DarkCyan;
                Console.WriteLine($"Cache Changed, Added: {e.AccountsAdded.Count()} Removed: {e.AccountsRemoved.Count()}");
                Console.ResetColor();
            };

            AuthenticationResult result;

            while (true)
            {
                // Display menu
                Console.WriteLine($@"
                        1. Acquire Token using Username and Password - for TEST only, do not use in production!
                        2. Acquire Token using Device Code Flow
                        3. Acquire Token Interactive
                        4. Acquire Token Silent
                        5. Display Accounts (reads the cache)
                        6. Acquire Token U/P and Silent in a loop                        
                        7. Use persistence layer to read / write any data
                        8. Use persistence layer to read / write any data with process-level lock
                        c. Clear cache
                        e. Expire Access Tokens (TEST only!)
                        x. Exit app
                    Enter your selection: ");
                char.TryParse(Console.ReadLine(), out var selection);
                try
                {
                    switch (selection)
                    {
                    case '1':     //  Acquire Token using Username and Password (requires config)

                        // IMPORTANT: you should ALWAYS try to get a token silently first

                        result = await AcquireTokenROPCAsync(pca).ConfigureAwait(false);

                        DisplayResult(result);

                        break;

                    case '2':     // Device Code Flow
                                  // IMPORTANT: you should ALWAYS try to get a token silently first

                        result = await pca.AcquireTokenWithDeviceCode(Config.Scopes, (dcr) =>
                        {
                            Console.BackgroundColor = ConsoleColor.DarkCyan;
                            Console.WriteLine(dcr.Message);
                            Console.ResetColor();

                            return(Task.FromResult(1));
                        }).ExecuteAsync().ConfigureAwait(false);

                        DisplayResult(result);

                        break;

                    case '3':     // Interactive
                                  // IMPORTANT: you should ALWAYS try to get a token silently first

                        result = await pca.AcquireTokenInteractive(Config.Scopes)
                                 .ExecuteAsync()
                                 .ConfigureAwait(false);

                        DisplayResult(result);
                        break;

                    case '4':     // Silent


                        Console.WriteLine("Getting all the accounts. This reads the cache");
                        var accounts = await pca.GetAccountsAsync().ConfigureAwait(false);

                        var firstAccount = accounts.FirstOrDefault();

                        // this is expected to fail when account is null
                        result = await pca.AcquireTokenSilent(Config.Scopes, firstAccount)
                                 .ExecuteAsync()
                                 .ConfigureAwait(false);

                        DisplayResult(result);
                        break;

                    case '5':     // Display Accounts
                        Console.Clear();
                        var accounts2 = await pca.GetAccountsAsync().ConfigureAwait(false);

                        if (!accounts2.Any())
                        {
                            Console.WriteLine("No accounts were found in the cache.");
                        }

                        foreach (var acc in accounts2)
                        {
                            Console.WriteLine($"Account for {acc.Username}");
                        }
                        break;

                    case '6':     // U/P and Silent in a loop
                        Console.WriteLine("CTRL-C to stop...");

#pragma warning disable CS0618 // Type or member is obsolete
                        cacheHelper.Clear();
#pragma warning restore CS0618 // Type or member is obsolete


                        var pca2 = CreatePublicClient("https://login.microsoftonline.com/organizations");
                        var pca3 = CreatePublicClient("https://login.microsoftonline.com/organizations");
                        cacheHelper.RegisterCache(pca2.UserTokenCache);
                        cacheHelper.RegisterCache(pca3.UserTokenCache);

                        while (true)
                        {
                            await Task.WhenAll(
                                RunRopcAndSilentAsync("PCA_1", pca),
                                RunRopcAndSilentAsync("PCA_2", pca2),
                                RunRopcAndSilentAsync("PCA_3", pca3)

                                ).ConfigureAwait(false);

                            Trace.Flush();
                            await Task.Delay(2000).ConfigureAwait(false);
                        }

                    case '7':

                        var storageProperties = new StorageCreationPropertiesBuilder(
                            Config.CacheFileName + ".other_secrets",
                            Config.CacheDir)
                                                .WithMacKeyChain(
                            Config.KeyChainServiceName + ".other_secrets",
                            Config.KeyChainAccountName);

                        Storage storage = Storage.Create(storageProperties.Build());
                        //string lockFilePath = Path.Combine(Config.CacheDir, Config.CacheFileName + ".other_secrets.lockfile");

                        byte[] secretBytes = Encoding.UTF8.GetBytes("secret");
                        Console.WriteLine("Writing...");
                        storage.WriteData(secretBytes);

                        Console.WriteLine("Writing again...");
                        storage.WriteData(secretBytes);


                        Console.WriteLine("Reading...");
                        var data = storage.ReadData();
                        Console.WriteLine("Read: " + Encoding.UTF8.GetString(data));

                        Console.WriteLine("Deleting...");
                        storage.Clear();


                        break;


                    case '8':

                        storageProperties = new StorageCreationPropertiesBuilder(
                            Config.CacheFileName + ".other_secrets2",
                            Config.CacheDir)
                                            .WithMacKeyChain(
                            Config.KeyChainServiceName + ".other_secrets2",
                            Config.KeyChainAccountName);

                        storage = Storage.Create(storageProperties.Build());

                        string lockFilePath = Path.Combine(Config.CacheDir, Config.CacheFileName + ".lockfile");

                        using (new CrossPlatLock(lockFilePath))     // cross-process only
                        {
                            secretBytes = Encoding.UTF8.GetBytes("secret");
                            Console.WriteLine("Writing...");
                            storage.WriteData(secretBytes);

                            Console.WriteLine("Writing again...");
                            storage.WriteData(secretBytes);

                            // if another process (not thread!) attempts to read / write this secret
                            // and uses the CrossPlatLock mechanism, it will wait for the lock to be released first
                            await Task.Delay(1000).ConfigureAwait(false);

                            Console.WriteLine("Reading...");
                            data = storage.ReadData();
                            Console.WriteLine("Read: " + Encoding.UTF8.GetString(data));

                            Console.WriteLine("Deleting...");
                            storage.Clear();
                        }     // lock released

                        break;

                    case 'c':
                        var accounts4 = await pca.GetAccountsAsync().ConfigureAwait(false);

                        foreach (var acc in accounts4)
                        {
                            Console.WriteLine($"Removing account for {acc.Username}");
                            await pca.RemoveAsync(acc).ConfigureAwait(false);
                        }
                        Console.Clear();

                        break;

                    case 'e':     // This is only meant for testing purposes

                        // do smth that loads the cache first
                        await pca.GetAccountsAsync().ConfigureAwait(false);

                        DateTimeOffset expiredValue = DateTimeOffset.UtcNow.AddMonths(-1);

                        var accessor = pca.UserTokenCache.GetType()
                                       .GetRuntimeProperties()
                                       .Single(p => p.Name == "Microsoft.Identity.Client.ITokenCacheInternal.Accessor")
                                       .GetValue(pca.UserTokenCache);

                        var internalAccessTokens = accessor.GetType().GetMethod("GetAllAccessTokens").Invoke(accessor, new object[] { null }) as IEnumerable <object>;

                        foreach (var internalAt in internalAccessTokens)
                        {
                            internalAt.GetType().GetRuntimeMethods().Single(m => m.Name == "set_ExpiresOn").Invoke(internalAt, new object[] { expiredValue });
                            accessor.GetType().GetMethod("SaveAccessToken").Invoke(accessor, new[] { internalAt });
                        }

                        var ctor = typeof(TokenCacheNotificationArgs).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).Single();

                        var notificationArgs = ctor.Invoke(new object[] { pca.UserTokenCache, Config.ClientId, null, true, false, true, null, null, null });
                        var task             = pca.UserTokenCache.GetType().GetRuntimeMethods()
                                               .Single(m => m.Name == "Microsoft.Identity.Client.ITokenCacheInternal.OnAfterAccessAsync")
                                               .Invoke(pca.UserTokenCache, new[] { notificationArgs });

                        await(task as Task).ConfigureAwait(false);
                        break;

                    case 'x':
                        return;
                    }
                }
                catch (Exception ex)
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine("Exception : " + ex);
                    Console.ResetColor();
                    Console.WriteLine("Hit Enter to continue");

                    Console.Read();
                }
            }
        }