I need help with how to retrieve a stored MSAL token so I can reuse it across executions of my app.
Scenario:
I am following this Microsoft demo which shows how to use MSAL to obtain an Oauth2 token for authentication to EWS in a console app. It works fine, and pops up an interactive login window to obtain the token each time I run the app.
I now want to make use of the same token (or its refresh token, if the original one has expired) in a later, independent execution of the app.
Ultimately, I want to implement an app where users provide the initial interactive Oauth login via my web UI, and from that point on I store the token for a background app which interacts with their EWS mailbox, and performs the refresh when needed.
What I'm trying:
I have been trying to make sense of this "naive implementation" of local file-based token caching. I can see that it's creating a local file, but how can I make it check this file in a later execution, and use the token that it's stored there. I've then found my way to this MSAL extension library, but it has no documentation and I can't even seem to adapt from its tests.
My code:
static async System.Threading.Tasks.Task MainAsync(string[] args)
{
// Configure the MSAL client to get tokens
var pcaOptions = new PublicClientApplicationOptions
{
ClientId = ConfigurationManager.AppSettings["appId"],
TenantId = ConfigurationManager.AppSettings["tenantId"]
};
var pca = PublicClientApplicationBuilder
.CreateWithApplicationOptions(pcaOptions).Build();
TokenCacheHelper.EnableSerialization(pca.UserTokenCache); // added based on 'naive implementation'
var ewsScopes = new string[] { "https://outlook.office.com/EWS.AccessAsUser.All" };
try
{
// Make the interactive token request
var authResult = await pca.AcquireTokenInteractive(ewsScopes).ExecuteAsync(); // must have to change this to something else that is aware of the cache?
// Configure the ExchangeService with the access token
var ewsClient = new ExchangeService();
ewsClient.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
ewsClient.Credentials = new OAuthCredentials(authResult.AccessToken);
// Make an EWS call
// ... do stuff in EWS ...
}
}
Helper code (from 'naive implementation')
static class TokenCacheHelper
{
public static void EnableSerialization(ITokenCache tokenCache)
{
tokenCache.SetBeforeAccess(BeforeAccessNotification);
tokenCache.SetAfterAccess(AfterAccessNotification);
}
/// <summary>
/// Path to the token cache
/// </summary>
public static readonly string CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + ".msalcache.bin3";
private static readonly object FileLock = new object();
private static void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
lock (FileLock)
{
args.TokenCache.DeserializeMsalV3(File.Exists(CacheFilePath)
? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath),
null,
DataProtectionScope.CurrentUser)
: null);
}
}
private static void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if the access operation resulted in a cache update
if (args.HasStateChanged)
{
lock (FileLock)
{
// reflect changesgs in the persistent store
File.WriteAllBytes(CacheFilePath,
ProtectedData.Protect(args.TokenCache.SerializeMsalV3(),
null,
DataProtectionScope.CurrentUser)
);
}
}
}
}