I am getting confused. I recently upgraded my Microsoft Graph and associated Nuget packages and they had some breaking changes for MSAL v3.
I have seen this article and added the following to my TokenCacheHelp
class so that it now looks like:
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation.
// All rights reserved.
//
// This code is licensed under the MIT License.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//------------------------------------------------------------------------------
using System.IO;
using System.Security.Cryptography;
using Microsoft.Identity.Client;
namespace OutlookCalIFConsole
{
static class TokenCacheHelper
{
/// <summary>
/// Get the user token cache
/// </summary>
/// <returns></returns>
public static TokenCache GetUserCache()
{
if (usertokenCache == null)
{
usertokenCache = new TokenCache();
usertokenCache.SetBeforeAccess(BeforeAccessNotification);
usertokenCache.SetAfterAccess(AfterAccessNotification);
}
return usertokenCache;
}
static TokenCache usertokenCache;
/// <summary>
/// Path to the token cache
/// </summary>
public static string CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + "msalcache.txt";
private static readonly object FileLock = new object();
public static void EnableSerialization(ITokenCache tokenCache)
{
tokenCache.SetBeforeAccess(BeforeAccessNotification);
tokenCache.SetAfterAccess(AfterAccessNotification);
}
public static void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
lock (FileLock)
{
//args.TokenCache.Deserialize(File.Exists(CacheFilePath)
// ? File.ReadAllBytes(CacheFilePath)
// : null);
// See: https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-net-token-cache-serialization
args.TokenCache.DeserializeMsalV3(File.Exists(CacheFilePath)
? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath),
null,
DataProtectionScope.CurrentUser)
: null);
}
}
public static void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if the access operation resulted in a cache update
if (args.HasStateChanged)
{
lock (FileLock)
{
// reflect changes in the persistent store
//File.WriteAllBytes(CacheFilePath, args.TokenCache.Serialize());
// once the write operation takes place restore the HasStateChanged bit to false
//args.TokenCache.HasStateChanged = false;
// See: https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-net-token-cache-serialization
// reflect changes in the persistent store
File.WriteAllBytes(CacheFilePath,
ProtectedData.Protect(args.TokenCache.SerializeMsalV3(),
null,
DataProtectionScope.CurrentUser)
);
}
}
}
}
}
But thinks are not workign quite right. At the moment I have:
IPublicClientApplication PublicClientApp = null;
public Outlook()
{
TokenCacheHelper.CacheFilePath = Program.Options.TokenCachePath;
//PublicClientApp = new PublicClientApplication(_AppID, "https://login.microsoftonline.com/common", TokenCacheHelper.GetUserCache());
PublicClientApp = PublicClientApplicationBuilder.Create(_AppID).Build();
TokenCacheHelper.EnableSerialization(TokenCacheHelper.GetUserCache());
}
In the article it said to use PublicClientApp.GetUserCache
but this value is empty. So I used TokenCacheHelper.GetUserCache()
but it is not working. When I start the utility it shows the authorisation screen. But it will not cache this value anymore and keeps showing the authorisation screen.
What else must I do?
I had to change to:
IPublicClientApplication PublicClientApp = null;
public Outlook()
{
PublicClientApp = PublicClientApplicationBuilder.Create(_AppID).Build();
TokenCacheHelper.EnableSerialization(PublicClientApp.UserTokenCache);
}
The key was removing the TokenCacheHelper.CacheFilePath = Program.Options.TokenCachePath;
call. I noticed the article stated:
Important MSAL.NET creates token caches for you and provides you with the
IToken
cache. Then, you read the caches using the application'sUserTokenCache
andAppTokenCache
properties. You are not supposed to implement the interface yourself.
Actually, that was not the reason. This code is fine:
IPublicClientApplication PublicClientApp = null;
public Outlook()
{
PublicClientApp = PublicClientApplicationBuilder.Create(_AppID).Build();
TokenCacheHelper.CacheFilePath = Program.Options.TokenCachePath;
TokenCacheHelper.EnableSerialization(PublicClientApp.UserTokenCache);
}
I had two issues:
I am able to specify my own path for the cache file and it uses it. It is working fine consistently for me and for my users.
For Microsoft.Graph
5.x I had to move some of my code into the new TokenProvider
class but the concept is still the same.