Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Token cache serialization in MSAL.NET is not working

I am facing some issues when trying to serialize the tokencache, returned from authenticating with MSAL. I would appreciate any help, since i don't really understand what i am doing wrong. Here is our situation/problem:

We are currently using ADAL to allow users to authenticate to their SharePoint Online accounts from our desktop application, but want to switch to MSAL.

We have implemented two possible authentication flows. A publicClientApplication, to allow the user authenticating with its currently active Microsoft credentials and a ConfidentialClientApplication, that allows to authenticate with the use of a certificat as you can see in the catch block of the code below:

                try
                {
                     Debugger.Launch();
                    if (certificate != null)
                    { 
                        IConfidentialClientApplication m_authContext = ConfidentialClientApplicationBuilder.Create(pClientID)
                            .WithTenantId(m_tenant).WithCertificate(certificate).WithRedirectUri(pClientRedirectURI).Build();
                        var accounts = await m_authContext.GetAccountsAsync();
                        authResult = await m_authContext.AcquireTokenSilent(m_scope, accounts.FirstOrDefault()).ExecuteAsync();
                    }
                    else
                    {
                        IPublicClientApplication m_authContext = PublicClientApplicationBuilder.Create(pClientID).WithTenantId(m_tenant).WithRedirectUri(pClientRedirectURI).Build();
                        var accounts = await m_authContext.GetAccountsAsync();
                        authResult = await m_authContext.AcquireTokenSilent(m_scope, accounts.FirstOrDefault()).ExecuteAsync();
                    }
                }
                catch
                {   
                    if (certificate != null)
                    { 
                        IConfidentialClientApplication m_authContext = ConfidentialClientApplicationBuilder.Create(pClientID)
                            .WithTenantId(m_tenant).WithCertificate(certificate).WithRedirectUri(pClientRedirectURI).Build();
                        TokenCacheHelper.EnableSerialization(m_authContext.AppTokenCache);
                        authResult = await m_authContext.AcquireTokenForClient(m_scope).WithForceRefresh(true).ExecuteAsync();
                    }
                    else
                    {
                        IPublicClientApplication m_authContext = PublicClientApplicationBuilder.Create(pClientID)
                            .WithTenantId(m_tenant).WithRedirectUri(pClientRedirectURI).Build();
                        TokenCacheHelper.EnableSerialization(m_authContext.UserTokenCache);
                        authResult = await m_authContext.AcquireTokenInteractive(m_scope).ExecuteAsync();
                    }
                } 

So far, the code works fine and the initial authentication is successful in both cases. The problem now is that we would like to persist the acquired tokens, so that the next time the user starts our program and tries to access SharePoint he does not have to authenticate again. But trying to authenticate silent with the use of a prior stored token does not work, neither for the public nor the confidential application. The serialization of the AfterAccessNotification however does seem to work, as at least something gets written into the cache file. But reading this data back does not. One thing to point out is that GetAccountsAsync() will always return 0.

What i have read so far, from the Microsoft documentation and other questions here, is that of course the inmemory cache of the applications will get lost when recreating it and the solution seems to be the implementation of the TokenCacheHelper. Our helper class is implemented as its suggested in the documentation:

    static class TokenCacheHelper
    {
        public static readonly string CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + "msalcache.txt";
        private static readonly object FileLock = new object();

        public static void EnableSerialization(ITokenCache tokenCache)
        {
            Debugger.Launch();
            tokenCache.SetBeforeAccess(BeforeAccessNotification);
            tokenCache.SetAfterAccess(AfterAccessNotification);
        }

        private static void BeforeAccessNotification(TokenCacheNotificationArgs args)
        {
            args.TokenCache.DeserializeMsalV3(File.Exists(CacheFilePath)
                        ? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath),
                                                  null,
                                                  DataProtectionScope.CurrentUser)
                       : null);
        }

        private static void AfterAccessNotification(TokenCacheNotificationArgs args)
        {
            Debugger.Launch();
            if (args.HasStateChanged)
            {
                lock (FileLock)
                {
                    // reflect changesgs in the persistent store
                    File.WriteAllBytes(CacheFilePath,
                                        ProtectedData.Protect(args.TokenCache.SerializeMsalV3(),
                                                                null,
                                                                DataProtectionScope.CurrentUser)
                                        );
                }
            }
        }

    }

Am i having a major misunderstanding on the use of the TokenCacheHelper or something else here? Or is there a more simple way on persisting the tokencache? This seemed to be far less complicated with the use of ADAL.

Thank you very much for your help.

like image 488
Mira Avatar asked Jul 15 '21 13:07

Mira


People also ask

How do I get the Msal refresh token?

If the refresh token is expired, MSAL will attempt to retrieve an access tokens silently using a hidden iframe. This will use the sid or username in the account's claims object to retrieve a hint about the user's session.

What is Msal token cache?

After Microsoft Authentication Library (MSAL) acquires a token, it caches that token. Public client applications (desktop and mobile apps) should try to get a token from the cache before acquiring a token by another method. Acquisition methods on confidential client applications manage the cache themselves.

How do I clear token cache?

To clear token cache that is made by the Acquire TokenAsync call, you could use the method authContext. TokenCache. Clear(); to make this.

What is token caching?

Caching is essentially an integral part in API request handling. This reduces the cost of database calls and other costly operations within or among different components by maintaining the requests and token details in memory. (

Why doesn't MSAL support token serialization?

And MSAL can't implement a general-purpose serialization mechanism. For example, websites might choose to store tokens in a Redis cache, or desktop apps might store tokens in an encrypted file. So serialization isn't provided out of the box. To have a persistent token cache application in .NET desktop or .NET Core, customize the serialization.

What is the default token cache in MSAL?

In MSAL.NET, an in-memory token cache is provided by default. Serialization is provided by default for platforms where secure storage is available for a user as part of the platform: Universal Windows Platform (UWP), Xamarin.iOS, and Xamarin.Android.

How do I serialize the token cache of a public client?

Since MSAL.NET v2.x you have several options for serializing the token cache of a public client. You can serialize the cache only to the MSAL.NET format (the unified format cache is common across MSAL and the platforms).

What classes and interfaces are used in token cache serialization?

The following classes and interfaces are used in token cache serialization: ITokenCache defines events to subscribe to token cache serialization requests and methods to serialize or deserialize the cache at various formats (ADAL v3.0, MSAL 2.x, and MSAL 3.x = ADAL v5.0).


1 Answers

We found out what was causing the problem. There was simply a call of TokenCacheHelper.EnableSerialization(m_authContext.UserTokenCache); missing before trying to acquire the token silent.

like image 139
Mira Avatar answered Sep 20 '22 12:09

Mira