Microsoft Authentication Library for .NET でリフレッシュトークンを永続化する

Microsoft Authentication Library for .NET(MSAL.NET) は、リフレッシュトークンを内部でキャッシュしていて、それを使ってアクセストークンを再取得できる。

ただ、キャッシュはメモリ上にしかないので、そのままだとプロセス終了したら失われてしまう。次回起動時もアクセストークンをサイレントに取得したいなら、永続化しないと。

Microsoft.Identity.Client.Extensions.Msal というパッケージを導入すれば、キャッシュを永続化できそう。

www.nuget.org

Microsoft Graph を呼び出すサンプルに組み込んでみた。

using System.Net.Http.Headers;
using Microsoft.Graph;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Extensions.Msal;

// Microsoft Todo のタスクとリストを CRUD するには
// Tasks.ReadWrite の許可が必要。
// リフレッシュトークンを取得するためには
// offline_access も必要。
var scopes = new[]
{
    "offline_access",
    "Tasks.ReadWrite",
};

// 一般ユーザーの Microsoft Todo にアクセスするときは
// common を指定すれば良いみたい。
var tenantId = "common";

// Azure AD で登録したアプリケーションの ID (Client ID)
var clientId = "YOUR_CLIENT_ID";

// Web ブラウザでログインするときのメールアドレス。
// ちゃんと対応するときは、この情報も永続化しておく必要がある。
var loginHint = "your-address@outlook.jp";

var pca = PublicClientApplicationBuilder.Create(clientId)
    .WithTenantId(tenantId)
    .WithRedirectUri("http://localhost")
    .Build();

// キャッシュをシリアライズ・デシリアライズできるようにする。
var storageProperties = new StorageCreationPropertiesBuilder(
    "UserTokenCache",
    AppContext.BaseDirectory)
    .Build();
var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties);
cacheHelper.RegisterCache(pca.UserTokenCache);

AuthenticationResult? authResult = null;
try
{
    // キャッシュされているリフレッシュトークンを使って、
    // 新しいアクセストークンを取得。
    authResult = await pca.AcquireTokenSilent(scopes, loginHint)
        .WithForceRefresh(true)
        .ExecuteAsync();
    Console.WriteLine($"ExpiresOn: {authResult.ExpiresOn}");
}
catch (MsalClientException ex)
{
    // アクセストークンを取得できなかったら、
    // Web ブラウザを表示して、
    // ユーザーに Microsoft アカウントにログインし、
    // アクセストークンを取得。
    authResult = await pca.AcquireTokenInteractive(scopes).ExecuteAsync();
    Console.WriteLine($"ExpiresOn: {authResult.ExpiresOn}");
}

// DelegateAuthenticationProvider を使って、
// 取得しておいたアクセストークンを Authorization ヘッダーにセットして
// Microsoft Graph を呼び出せるようにする。
var authProvider = new DelegateAuthenticationProvider(request =>
{
    request.Headers.Authorization = new AuthenticationHeaderValue(
        "Bearer",
        authResult.AccessToken);
    return Task.CompletedTask;
});
var graphClient = new GraphServiceClient(authProvider);

// リスト一覧表示
var todoLists = await graphClient.Me
    .Todo
    .Lists
    .Request()
    .GetAsync();
foreach (var todoList in todoLists)
{
    Console.WriteLine($"既存のリスト: {todoList.DisplayName}");
}

Console.ReadLine();