Skip to content
This repository has been archived by the owner on Sep 7, 2023. It is now read-only.

Cross platform Token Cache

Jean-Marc Prieur edited this page Sep 23, 2020 · 26 revisions

Problem statement

MSAL requires developers to implement their own logic for persisting the token cache on .NET Core and .NET Classic. For more details about token cache serialization, check out our docs. Providing a secure location is a difficult problem, especially one that can be accessed from C# APIs on Mac and Linux.

Goals

  • Provide a robust, secure and configurable token cache persistence implementation across Windows, Mac and Linux for public client applications (rich clients, CLI applications etc.)
  • Token cache storage can be accessed by multiple processes concurrently.
  • Provide a higher-level event that signals when accounts are added or removed from the cache.

Non Goals

  • This implementation is not suitable for web app / web api scenarios, where storing the cache should be done in memory, Redis, Sql Server etc. Have a look at the web samples for server-side implementations.

Code

Referencing the NuGet package

Add the Microsoft.Identity.Client.Extensions.Msal NuGet package to your project.

Configuring the token cache

All the arguments are explained in the API docs. For an example, see this config in the sample app.

 var storageProperties =
     new StorageCreationPropertiesBuilder(Config.CacheFileName, Config.CacheDir, Config.ClientId)
     .WithLinuxKeyring(
         Config.LinuxKeyRingSchema,
         Config.LinuxKeyRingCollection,
         Config.LinuxKeyRingLabel,
         Config.LinuxKeyRingAttr1,
         Config.LinuxKeyRingAttr2)
     .WithMacKeyChain(
         Config.KeyChainServiceName,
         Config.KeyChainAccountName)
     .Build();

 IPublicClientApplication pca = PublicClientApplicationBuilder.Create(clientId)
    .WithAuthority(Config.Authority)
    .WithRedirectUri("http://localhost")  // make sure to register this redirect URI for the interactive login 
    .Build();
    

// This hooks up the cross-platform cache into MSAL
var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties );
cacheHelper.RegisterCache(pca.UserTokenCache);
         

Subscribing to the CacheChanged event

While the cache may be accessed many times by MSAL, your application typically only cares if a new account is added or removed.

cacheHelper.CacheChanged += (object sender, CacheChangedEventArgs args) =>
{
    Console.WriteLine($"Cache Changed, Added: {args.AccountsAdded.Count()} Removed: {args.AccountsRemoved.Count()}");
};

event The image above shows 2 applications that use the same client ID sharing the token cache. One of the apps logs in a new user, and both apps get notified of an account being added to the cache.

Sample

Have a look at a simple console app using this token cache. We use this for testing on Windows, Mac and Linux.

Security Boundary

On Windows and Linux, the token cache is scoped to the user session, i.e. all applications running on behalf of the user can access the cache. Mac offers a more restricted scope, ensuring that only the application that created the cache can access it, and prompting the user if others apps want access.

Encryption and storage

Windows

DPAPI is used to encrypt the token cache. The encrypted data is stored in a file in LocalAppData folder.

Mac

The token cache is stored in the Mac KeyChain, which encrypts it on behalf of the user and the application itself.

Linux

The token cache is stored in the a wallet such as Gnome Keyring or KWallet using LibSecret. Its contents can be visualised using tools such as Gnome Seahorse.

linux token cache

Linux Fallback

KeyRings does not work in headless mode (e.g. when connected over SSH or when running Linux in a container) due to a dependency on X11. To overcome this, a fallback to a plaintext file can be configured. See this example for how to configure it.

Sharing the cache between multiple apps

Our vision for Single Sign On (SSO) across multiple applications is that a 3rd application - a broker - must intervene to perform account and device management. Today, there are brokers for Android (Authenticator, Company Portal), iOS (Authenticator) and Windows (WAM), but using brokers from .NET will require more investment and time.

As a stop gap solution, if you want SSO between your .NET, python or Java apps, consider using the same client ID for all your apps. Note that once you go down this route, you cannot make individual changes to applications (e.g. you cannot enable MFA for one app but not the others).

Other language implementations

Similar functionality exists in Java and Python libraries:

Architecture

For an architectural overview see cache architecture diagram

Cross process synchronisation is done using file locks, since this is the only mechanism available on all platforms. The eventing is also done using files and a file watcher. For the event to work, the cache is deserialized a second time.

Battle tested

Visual Studio family of apps, Azure Powershell and Azure CLI all use this approach.

Clone this wiki locally