ここではUWPアプリからMicrosoft Graphのユーザー認証を行う方法について説明します(ユーザー認証用のユーティリティークラスのコード例もお示ししています)。なお、Microsoft Graph APIにはMicrosoft Graph .NET Client Libraryを用いています。
動作確認環境
- Microsoft.Identity.Client 4.28.1
- Microsoft.Graph 3.27.0
※ 本コードはWinUI 3.0ライブラリを用いて作成したUWPアプリでは動作しません。
[toc]Microsoft Graphのユーザー認証の概要
Microsoft GraphではMicrosoft ID プラットフォームを用いてユーザー認証を行います。その認証ライブラリとしてMicrosoft Authentication Library (MSAL)が用意されています。その.NET用のAPIは、Azure AD LibraryのMicrosoft.Identity.Client 名前空間に用意されています。
Microsoft ID プラットフォームを用いたユーザー認証の準備
必要なライブラリを追加する
ここでは ユーザー認証用のライブラリとして「Microsoft Authentication Library」を、Microsoft GraphのAPIとして「Microsoft Graph .NET Client Library」を使用するので、まずはその2つのライブラリを追加しておく必要があります。
Visual Studioを使っている場合は、NuGetから以下の2つをプロジェクトに追加してください。
- Microsoft.Identity.Client
- Microsoft.Graph
Microsoft Azure ポータルにアプリを登録する
Microsoft ID プラットフォームを用いてユーザー認証できるようにするには、あらかじめアプリをMicrosoftに登録しておく必要があります。Microsoft Azure ポータルからアプリを登録しましょう。
ここではGraphTestというアプリ名で登録しています。どの種類のアカウントからアクセスできるようにするか(サポートされているアカウントの種類)は自分のアプリに合ったものを選んでください。リダイレクトURIは後からも設定できるのでここでは空欄で構いません。
ユーザー認証のためのユーティリティークラスの作成
UWPアプリを用いてMicrosoft Graphのユーザー認証を行い、Microsoft Graph APIを用いる流れは次のようになります。
Microsoft Graph APIを使用する際は、まずサインインしてアクセストークンを取得し、HTTPリクエストのヘッダーにそのアクセストークンを含めてHTTPメソッドを作成します。Microsoft Graph .NET Client Libraryでは認証済みのトークンなどを含めたGraphServiceClientインスタンスに対してリクエストを送ることでMicrosoft Graph APIのリクエストを実行します。
ここでは、ユーザー認証を行いGraphServiceClientインスタンスを取得するユーティリティークラスを作成してみましょう。
コード例
using Microsoft.Graph;
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class AuthenticationHelper
{
//アプリケーション構成オプションの設定
private const string ClientId = "********-****-****-****-************";
private const string Instance = "https://login.microsoftonline.com/";
private const string Tenant = "consumers";
private const string Authority = Instance + Tenant;
private const string DefaultRedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient";
//スコープの設定
private static string[] Scopes { get; } = { "User.Read" };
private static IPublicClientApplication publicClientApp;
private static GraphServiceClient authenticatedClient = null;
//認証済みのGraphServiceClientインスタンスを取得するメソッド
public async static Task<GraphServiceClient> GetAuthenticatedClientAsync()
{
//認証済みのGraphServiceClientインスタンスがない場合は、
//サインインして新たに生成する
if (authenticatedClient == null)
{
authenticatedClient = new GraphServiceClient(
new DelegateAuthenticationProvider(async (requestMessage) =>
{
requestMessage.Headers.Authorization
= new AuthenticationHeaderValue("bearer", await GetTokenForUserAsync(Scopes));
}));
}
return await Task.FromResult(authenticatedClient);
}
//サインインしてアクセストークンを取得するメソッド
private static async Task<string> GetTokenForUserAsync(string[] scopes)
{
AuthenticationResult authResult;
// PublicClientApplicationを生成(MSALの初期化)
publicClientApp = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority(Authority)
.WithRedirectUri(DefaultRedirectUri)
.Build();
IEnumerable<IAccount> accounts
= await publicClientApp.GetAccountsAsync().ConfigureAwait(false);
IAccount firstAccount = accounts.FirstOrDefault();
//まずは自動でサインインできるか試す
try
{
authResult
= await publicClientApp.AcquireTokenSilent(scopes, firstAccount)
.ExecuteAsync();
}
//自動でサインインできなければ、対話ウインドウでサインインする
catch (MsalUiRequiredException)
{
authResult = await publicClientApp.AcquireTokenInteractive(scopes)
.ExecuteAsync()
.ConfigureAwait(false);
}
return authResult.AccessToken;
}
//サインアウトする
public static async Task SignOutAsync()
{
IEnumerable<IAccount> accounts
= await publicClientApp.GetAccountsAsync().ConfigureAwait(false);
IAccount firstAccount = accounts.FirstOrDefault();
await publicClientApp.RemoveAsync(firstAccount).ConfigureAwait(false);
authenticatedClient = null;
}
}
コード例におけるAuthenticationHelperクラスのGetAuthenticatedClientメソッドでユーザー認証を行って、GraphServiceClientインスタンスを取得するすることができます。
それでは、このコードについて順番に解説していきます。なお、このコードは以下のサイトのものを参考にしています。
アプリケーション構成オプション・スコープの設定
アプリケーション構成オプションには「クライアントID」「Authority」「リダイレクトURI」があり、14-18行目で設定を行っています。
//アプリケーション構成オプションの設定
private const string ClientId = "********-****-****-****-************";
private const string Instance = "https://login.microsoftonline.com/";
private const string Tenant = "consumers";
private const string Authority = Instance + Tenant;
private const string DefaultRedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient";
//スコープの設定
private static string[] Scopes { get; } = { "User.Read" };
クライアントID
クライアントIDはMicrosoft Azure ポータルにアプリを登録すると発行されるIDです。コード中のClientId定数を自分のアプリのクライアントIDに置き換えてください。
Authority
AuthorityとはMSALがトークンを要求できるディレクトリを示すURLのことで、「インスタンス」と「テナント」の組み合わせから成り立っています。特に指定しない場合はインスタンスはAzure パブリッククラウドインスタンスの「https://login.microsoftonline.com/」となります。
また、テナントは対象とするユーザーによって以下のように指定します(コード例ではテナントを「consumers」としています)。なお、この指定はMicrosoft Azure ポータルにおけるアプリの登録と一致させておく必要があります。
common | 職場および学校アカウント、または個人用 Microsoft アカウントを持つユーザー |
organizations | 職場および学校アカウントを持つユーザー |
consumers | 個人用 Microsoft アカウントのみを持つユーザー |
リダイレクトURI
Microsoft Azure ポータルでアプリのリダイレクトURIとして「https://login.microsoftonline.com/common/oauth2/nativeclient」のチェックを入れておき、DefaultRedirectUri定数にも同じURIを代入しておきます。
アプリケーション構成オプションの詳細については以下のリファレンスもご参照ください。
スコープの設定
アプリからアクセスできる情報についてあらかじめ設定しておきます。Microsoft Azure ポータルの「APIのアクセス許可」からアクセス許可を指定しておき、それと同じスコープをコード中のScopesプロパティに設定しておいてください。
コード例ではMicrosoft Graphにおけるユーザー情報を取得する「User.Read」をMicrosoft Azure ポータルで登録しておき、それをコード上でも指定しています。
サインイン・認証トークンの取得
続いて、サインインして認証トークンを取得するGetTokenForUserAsyncメソッドを作成しましょう。
//サインインしてアクセストークンを取得するメソッド
private static async Task<string> GetTokenForUserAsync(string[] scopes)
{
AuthenticationResult authResult;
// PublicClientApplicationを生成(MSALの初期化)
publicClientApp = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority(Authority)
.WithRedirectUri(DefaultRedirectUri)
.Build();
IEnumerable<IAccount> accounts
= await publicClientApp.GetAccountsAsync().ConfigureAwait(false);
IAccount firstAccount = accounts.FirstOrDefault();
//まずは自動でサインインできるか試す
try
{
authResult
= await publicClientApp.AcquireTokenSilent(scopes, firstAccount)
.ExecuteAsync();
}
//自動でサインインできなければ、対話ウインドウでサインインする
catch (MsalUiRequiredException)
{
authResult = await publicClientApp.AcquireTokenInteractive(scopes)
.ExecuteAsync()
.ConfigureAwait(false);
}
return authResult.AccessToken;
}
50-53行目で認証トークンを取得するためのPublicClientApplicationクラスのインスタンスを作成し、それにクライアントIDやAuthority、リダイレクトURIを設定しています。ここで、PublicClientApplicationクラスのインスタンスを作成するためには、まずPublicClientApplicationBuilderクラスのCreateメソッドで、クライアントIDを指定してそのインスタンスを作成します。その上で、PublicClientApplicationBuilderインスタンスのWithAuthorityメソッドとWithRedirectUriメソッドでそれぞれAuthorityとリダイレクトURIを指定します。
55-57行目ではGetAccountsAsyncメソッドで取得可能なすべてのIAccountオブジェクトを取得し、FirstOrDefaultメソッドで一番最初のもの(なければデフォルトのもの)を取得します。
PublicClientApplicationインスタンスにスコープとアカウントを指定して、まずはAcquireTokenSilentメソッドで対話ウインドウを表示させずにバックグラウンドでの自動サインインでの認証トークン取得を試みます(61-66行目)。しかし、初めてサインインする場合やサインインの有効期限が切れているような場合はアカウント・パスワードをユーザーに入力してもらう必要があります。この場合はAcquireTokenInteractiveメソッドを用いて対話ウインドウからサインインします(68-73行目)。
GraphServiceClientの取得
最後に認証トークンをもとにしたGraphServiceClientインスタンスを作成するGetAuthenticatedClientAsyncメソッドを作成しましょう。
//認証済みのGraphServiceClientインスタンスを取得するメソッド
public async static Task<GraphServiceClient> GetAuthenticatedClientAsync()
{
//認証済みのGraphServiceClientインスタンスがない場合は、
//サインインして新たに生成する
if (authenticatedClient == null)
{
authenticatedClient = new GraphServiceClient(
new DelegateAuthenticationProvider(async (requestMessage) =>
{
requestMessage.Headers.Authorization
= new AuthenticationHeaderValue("bearer", await GetTokenForUserAsync(Scopes));
}));
}
return await Task.FromResult(authenticatedClient);
}
ここではGraphServiceClientインスタンスが作成されていない場合に、新たにサインインを行って取得した認証トークンをヘッダーに含めたGraphServiceClientインスタンスを作成する処理を記述しています。なお、GraphServiceClientインスタンスが既に存在する場合はそれを返します。
GraphServiceClientクラスのコンストラクタ―ではIAuthenticationProvider型のオブジェクトを引数に取ります。ここでは、IAuthenticationProviderインターフェイスを実装したDelegateAuthenticationProviderクラスのインスタンスを引数に渡しましょう。DelegateAuthenticationProviderクラスのコンストラクタ―はAuthenticateRequestAsyncDelegateデリゲート型を引数に取るので、その処理をラムダ式で定義しています。ラムダ式では、HTTPリクエストのヘッダーの認証情報に先ほど取得した認証トークンをAuthenticationHeaderValueクラスのインスタンスとして指定しています。その際、認証トークンの種類(スキーム)も指定する必要がありますが、Microsoft ID プラットフォームでサポートされる種類は「bearer」のみであり、それを指定しています。
サインアウト
サインアウトの処理も作成しておきましょう。
//サインアウトする
public static async Task SignOutAsync()
{
IEnumerable<IAccount> accounts
= await publicClientApp.GetAccountsAsync().ConfigureAwait(false);
IAccount firstAccount = accounts.FirstOrDefault();
await publicClientApp.RemoveAsync(firstAccount).ConfigureAwait(false);
authenticatedClient = null;
}
まず、PublicClientApplicationインスタンスのアカウントをすべて取得し、そのうち一番最初のもの(なければデフォルトの値)をfirstAccount変数に代入しています(80-82行目)。その上で、PublicClientApplicationインスタンスからそのアカウントを削除します(83行目)。同時に取得したGraphServiceClientインスタンスもnullに設定しておきましょう(84行目)。
ユーティリティークラスの使用例
それでは、例として先ほど作成したユーザー情報を用いてサインインしたユーザー名を取得するプログラムを作成してみましょう。
string userName;
GraphServiceClient graphClient = await AuthenticationHelper.GetAuthenticatedClientAsync();
if (graphClient != null)
{
var user = await graphClient.Me.Request().GetAsync();
userName = user.DisplayName;
}
2行目でユーティリティークラスを用いてサインインしてGraphServiceClientのインスタンスを取得しています。GraphServiceClientのインスタンスさえ取得できれば自由にMicrosoft Graph APIのメソッドを用いることができるので、6,7行目でユーザー情報のうち表示名を取得しています。
UWP以外のプラットフォームにおけるユーザー認証の方法
ここではUWPアプリにおけるMicrosoft Graph APIのユーザー認証の方法を解説していますが、デスクトップアプリ(WPF)などプラットフォームごとにユーザー認証コードが異なります。
Microsoft Azure ポータルでアプリを登録し、「クイックスタート」から自分のアプリに合ったプラットフォームを選択すると、必要なクライアントIDなどをすべて自分のアプリに合わせてセットした状態でサンプルコードをダウンロードすることができます。
コメント