チュートリアル: Windows Presentation Foundation (WPF) デスクトップ アプリでユーザーをサインインし、Microsoft Graph を呼び出す

このチュートリアルでは、ユーザーのサインインを処理して Microsoft Graph API を呼び出すためのアクセス トークンを取得するネイティブ Windows デスクトップ .NET (XAML) アプリを作成します。

このガイドを完了すると、アプリケーションで個人アカウント (outlook.com、live.com など) を使用する保護された API を呼び出すことができるようになります。 このアプリケーションでは、Microsoft Entra ID を使用する会社または組織の職場および学校アカウントも使用します。

このチュートリアルの内容:

  • Visual Studio で Windows Presentation Foundation (WPF) プロジェクトを作成する
  • .NET 用 Microsoft 認証ライブラリ (MSAL) をインストールする
  • アプリケーションを登録する
  • ユーザーのサインインとサインアウトをサポートするコードを追加する
  • Microsoft Graph API を呼び出すコードを追加する
  • アプリケーションをテストする

前提条件

このガイドで生成されたサンプル アプリの動作

Screenshot of how the sample app generated by this tutorial works.

このガイドで作成するサンプル アプリケーションにより、Windows デスクトップ アプリケーションにおいて、Microsoft ID プラットフォーム エンドポイントからのトークンを受け付ける Microsoft Graph API または Web API に対してクエリを実行できるようになります。 このシナリオでは、Authorization ヘッダーを使用して HTTP 要求にトークンを追加します。 トークンの取得と更新は、Microsoft Authentication Library (MSAL) で処理されます。

保護された Web API にアクセスするためのトークン取得処理

ユーザーが認証されると、サンプル アプリケーションは、Microsoft Graph API または Microsoft ID プラットフォームによって保護されている Web API でのクエリに使用できるトークンを受け取ります。

Microsoft Graph などの API では、特定のリソースへのアクセスを許可するためにトークンが必要になります。 たとえば、トークンは、ユーザーのプロファイルの読み取り、ユーザーの予定表へのアクセス、メールの送信などに必要です。 アプリケーションでは、MSAL を使用してアクセス トークンを要求し、API スコープを指定することによってこれらのリソースにアクセスできます。 このアクセス トークンは、保護されたリソースに対するすべての呼び出しで HTTP Authorization ヘッダーに追加されます。

アクセス トークンのキャッシュと更新は MSAL が管理するため、アプリケーションが管理する必要はありません。

NuGet パッケージ

このガイドでは、次の NuGet パッケージを使用します。

ライブラリ 説明
Microsoft.Identity.Client Microsoft Authentication Library (MSAL.NET)

プロジェクトの設定

このセクションでは、Windows デスクトップ .NET アプリケーション (XAML) に "Microsoft でサインイン" を統合して、トークンを必要とする Web API でクエリを実行できるようにする方法を示すために、新しいプロジェクトを作成します。

作成するアプリケーションには、Microsoft Graph API を呼び出すボタン、結果を表示する領域、サインアウト ボタンが表示されます。

注意

代わりにこのサンプルの Visual Studio プロジェクトをダウンロードすることもできます。 プロジェクトをダウンロードしたら、構成手順に進んでサンプル コードを構成してから実行してください。

次の手順に従ってアプリケーションを作成します。

  1. Visual Studio を開く
  2. [スタート ウィンドウ] で、 [新しいプロジェクトの作成] を選択します。
  3. [すべての言語] ドロップダウンで、[C#] を選択します。
  4. WPF アプリ (.NET Framework) テンプレートを検索して選択し、[次へ] を選択します。
  5. [プロジェクト名] ボックスに、Win-App-calling-MsGraph などの名前を入力します。
  6. プロジェクトの [場所] を選択するか、既定のオプションをそのまま使用します。
  7. [フレームワーク] で、[.NET Framework 4.8] を選択します。
  8. [作成] を選択します

プロジェクトに MSAL を追加する

  1. Visual Studio で、 [ツール]>[NuGet パッケージ マネージャー]>[パッケージ マネージャー コンソール] の順に選択します。

  2. [パッケージ マネージャー コンソール] ウィンドウで、次の Azure PowerShell コマンドを貼り付けます。

    Install-Package Microsoft.Identity.Client -Pre
    

アプリケーションの登録

ヒント

この記事の手順は、開始するポータルによって若干異なる場合があります。

アプリケーションを登録して構成するには、次の手順に従います。

  1. アプリケーション開発者以上として Microsoft Entra 管理センターにサインインします。
  2. 複数のテナントにアクセスできる場合は、上部のメニューの [設定] アイコン を使い、[ディレクトリとサブスクリプション] メニューからアプリケーションを登録するテナントに切り替えます。
  3. [ID]>[アプリケーション]>[アプリの登録] を参照します。
  4. [新規登録] を選択します。
  5. アプリケーションの名前を入力します (例: Win-App-calling-MsGraph)。 この名前は、アプリのユーザーに表示される場合があります。また、後で変更することができます。
  6. [サポートされているアカウントの種類] セクションで、[任意の組織のディレクトリ (Microsoft Entra ディレクトリ - マルチテナント) 内のアカウントと、個人用の Microsoft アカウント (Skype、Xbox など)] を選択します。
  7. [登録] を選択します。
  8. [管理] で、 [認証]>[プラットフォームの追加] の順に選択します。
  9. [モバイル アプリケーションとデスクトップ アプリケーション] を選択します。
  10. [リダイレクト URI] セクションで、https://login.microsoftonline.com/common/oauth2/nativeclient を選択します。
  11. [構成] をクリックします。

MSAL を初期化するコードの追加

この手順では、トークンの処理など MSAL ライブラリとのやり取りを処理するクラスを作成します。

  1. App.xaml.cs ファイルを開き、クラスに MSAL の参照を追加します。

    using Microsoft.Identity.Client;
    
  2. App クラスを以下のように更新します。

    public partial class App : Application
    {
        static App()
        {
            _clientApp = PublicClientApplicationBuilder.Create(ClientId)
                .WithAuthority(AzureCloudInstance.AzurePublic, Tenant)
                .WithDefaultRedirectUri()
                .Build();
        }
    
        // Below are the clientId (Application Id) of your app registration and the tenant information.
        // You have to replace:
        // - the content of ClientID with the Application Id for your app registration
        // - the content of Tenant by the information about the accounts allowed to sign-in in your application:
        //   - For Work or School account in your org, use your tenant ID, or domain
        //   - for any Work or School accounts, use `organizations`
        //   - for any Work or School accounts, or Microsoft personal account, use `common`
        //   - for Microsoft Personal account, use consumers
        private static string ClientId = "Enter_the_Application_Id_here";
    
        private static string Tenant = "common";
    
        private static IPublicClientApplication _clientApp ;
    
        public static IPublicClientApplication PublicClientApp { get { return _clientApp; } }
    }
    

アプリケーション UI を作成する

このセクションでは、アプリケーションで、Microsoft Graph のような保護されたバックエンド サーバーに対してクエリを実行する方法を示します。

MainWindow.xaml ファイルは、プロジェクト テンプレートの一部として自動的に作成されます。 このファイルを開き、アプリケーションの <Grid> ノードを次のコードに置き換えます。

<Grid>
    <StackPanel Background="Azure">
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
            <Button x:Name="CallGraphButton" Content="Call Microsoft Graph API" HorizontalAlignment="Right" Padding="5" Click="CallGraphButton_Click" Margin="5" FontFamily="Segoe Ui"/>
            <Button x:Name="SignOutButton" Content="Sign-Out" HorizontalAlignment="Right" Padding="5" Click="SignOutButton_Click" Margin="5" Visibility="Collapsed" FontFamily="Segoe Ui"/>
        </StackPanel>
        <Label Content="API Call Results" Margin="0,0,0,-5" FontFamily="Segoe Ui" />
        <TextBox x:Name="ResultText" TextWrapping="Wrap" MinHeight="120" Margin="5" FontFamily="Segoe Ui"/>
        <Label Content="Token Info" Margin="0,0,0,-5" FontFamily="Segoe Ui" />
        <TextBox x:Name="TokenInfoText" TextWrapping="Wrap" MinHeight="70" Margin="5" FontFamily="Segoe Ui"/>
    </StackPanel>
</Grid>

MSAL を使用して Microsoft Graph API のトークンを取得する

このセクションでは、MSAL を使用して Microsoft Graph API のトークンを取得します。

  1. MainWindow.xaml.cs ファイルで、クラスに MSAL の参照を追加します。

    using Microsoft.Identity.Client;
    
  2. MainWindow クラス コードを次のコードに置き換えます。

    public partial class MainWindow : Window
    {
        //Set the API Endpoint to Graph 'me' endpoint
        string graphAPIEndpoint = "https://graph.microsoft.com/v1.0/me";
    
        //Set the scope for API call to user.read
        string[] scopes = new string[] { "user.read" };
    
    
        public MainWindow()
        {
            InitializeComponent();
        }
    
      /// <summary>
        /// Call AcquireToken - to acquire a token requiring user to sign-in
        /// </summary>
        private async void CallGraphButton_Click(object sender, RoutedEventArgs e)
        {
            AuthenticationResult authResult = null;
            var app = App.PublicClientApp;
            ResultText.Text = string.Empty;
            TokenInfoText.Text = string.Empty;
    
            var accounts = await app.GetAccountsAsync();
            var firstAccount = accounts.FirstOrDefault();
    
            try
            {
                authResult = await app.AcquireTokenSilent(scopes, firstAccount)
                    .ExecuteAsync();
            }
            catch (MsalUiRequiredException ex)
            {
                // A MsalUiRequiredException happened on AcquireTokenSilent.
                // This indicates you need to call AcquireTokenInteractive to acquire a token
                System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
    
                try
                {
                    authResult = await app.AcquireTokenInteractive(scopes)
                        .WithAccount(accounts.FirstOrDefault())
                        .WithPrompt(Prompt.SelectAccount)
                        .ExecuteAsync();
                }
                catch (MsalException msalex)
                {
                    ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
                }
            }
            catch (Exception ex)
            {
                ResultText.Text = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
                return;
            }
    
            if (authResult != null)
            {
                ResultText.Text = await GetHttpContentWithToken(graphAPIEndpoint, authResult.AccessToken);
                DisplayBasicTokenInfo(authResult);
                this.SignOutButton.Visibility = Visibility.Visible;
            }
        }
        }
    

詳細情報

ユーザー トークンを対話形式で取得する

AcquireTokenInteractive メソッドを呼び出すと、ユーザーにサインインを求めるウィンドウが表示されます。 通常、アプリケーションは、ユーザーが保護されたリソースに初めてアクセスするときに、対話形式でユーザーにサインインを求めます。 また、自動でのトークンの取得に失敗した場合 (ユーザーのパスワードが期限切れになっている場合など) にも、ユーザーはサインインする必要があります。

ユーザー トークンを自動で取得する

AcquireTokenSilent メソッドは、ユーザーの操作なしでトークンの取得と更新を処理します。 AcquireTokenInteractive が初めて実行された後、以降の呼び出しでは、保護されたリソースへのアクセスに使用するトークンを取得する際に、通常は AcquireTokenSilent メソッドを使用します。トークンを要求または更新する呼び出しが自動で行われるからです。

最終的に、AcquireTokenSilent メソッドは失敗します。 この失敗は、ユーザーがサインアウトしたか、別のデバイスでパスワードを変更したことが原因と考えられます。 ユーザーの操作によって解決できる問題が MSAL によって検出された場合、MSAL は MsalUiRequiredException 例外を発行します。 アプリケーションでは、この例外を 2 つの方法で処理できます。

  • すぐに AcquireTokenInteractive を呼び出します。 この呼び出しにより、ユーザーにサインインを求めます。 ユーザーに対して使用できるオフライン コンテンツがないオンライン アプリケーションでは、このパターンを使用します。 このセットアップで生成されるサンプルでは、このパターンに従います。これは、サンプルの初回実行時に実際の動作を確認できます。

  • アプリケーションはユーザーによって使用されたことがないため、PublicClientApp.Users.FirstOrDefault() には null 値が含まれ、MsalUiRequiredException 例外がスローされます。

  • サンプルのコードでは、AcquireTokenInteractive を呼び出してユーザーにサインインを求めることにより、この例外を処理します。

  • 対話形式でのサインインが必要であることをユーザーに視覚的に示すことで、ユーザーが適切なタイミングでサインインできるようにします。 または、アプリケーションが後で AcquireTokenSilent を再試行します。 多くの場合、このパターンは、ユーザーが中断することなく他のアプリケーション機能を使用できる場合に使用されます。 たとえば、アプリケーションでオフラインのコンテンツを使用可能な場合です。 この場合、保護されたリソースにアクセスしたり、古くなった情報を更新したりするために、サインインするタイミングをユーザーが決定できます。 また、一時的に使用できなくなっていたネットワークが回復したときに、アプリケーションが AcquireTokenSilent の再試行を決定することもできます。

取得したトークンを使用して Microsoft Graph API を呼び出す

次の新しいメソッドを MainWindow.xaml.cs に追加します。 このメソッドは、Authorize ヘッダーを使用した Graph API に対する GET 要求の実行に使用されます。

/// <summary>
/// Perform an HTTP GET request to a URL using an HTTP Authorization header
/// </summary>
/// <param name="url">The URL</param>
/// <param name="token">The token</param>
/// <returns>String containing the results of the GET operation</returns>
public async Task<string> GetHttpContentWithToken(string url, string token)
{
    var httpClient = new System.Net.Http.HttpClient();
    System.Net.Http.HttpResponseMessage response;
    try
    {
        var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, url);
        //Add the token in Authorization header
        request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
        response = await httpClient.SendAsync(request);
        var content = await response.Content.ReadAsStringAsync();
        return content;
    }
    catch (Exception ex)
    {
        return ex.ToString();
    }
}

保護された API に対する REST 呼び出しの実行についての詳細

このサンプル アプリケーションでは、GetHttpContentWithToken メソッドを使用して、トークンが必要な保護されたリソースに対して HTTP GET 要求を実行し、呼び出し元にその内容を返します。 このメソッドは、取得したトークンを HTTP Authorization ヘッダーに追加します。 このサンプルで使用するリソースは、ユーザーのプロファイル情報を表示する Microsoft Graph API me エンドポイントです。

ユーザーをサインアウトさせるメソッドを追加する

ユーザーをサインアウトさせるには、次のメソッドを MainWindow.xaml.cs ファイルに追加します。

/// <summary>
/// Sign out the current user
/// </summary>
private async void SignOutButton_Click(object sender, RoutedEventArgs e)
{
    var accounts = await App.PublicClientApp.GetAccountsAsync();

    if (accounts.Any())
    {
        try
        {
            await App.PublicClientApp.RemoveAsync(accounts.FirstOrDefault());
            this.ResultText.Text = "User has signed-out";
            this.CallGraphButton.Visibility = Visibility.Visible;
            this.SignOutButton.Visibility = Visibility.Collapsed;
        }
        catch (MsalException ex)
        {
            ResultText.Text = $"Error signing-out user: {ex.Message}";
        }
    }
}

ユーザーのサインアウトに関する詳細情報

SignOutButton_Click メソッドは、MSAL ユーザー キャッシュからユーザーを削除します。これは実質的に MSAL に現在のユーザーを忘れさせることになり、以降の要求が対話形式で行われる場合のみトークンの取得が成功します。

このサンプルのアプリケーションは単一ユーザーに対応していますが、MSAL は複数のアカウントで同時にサインインするシナリオをサポートしています。 例として、電子メール アプリケーションで 1 人のユーザーが複数のアカウントを持っている場合が挙げられます。

基本的なトークン情報を表示する

トークンについての基本的な情報を表示するには、次のメソッドを MainWindow.xaml.cs ファイルに追加します。

/// <summary>
/// Display basic information contained in the token
/// </summary>
private void DisplayBasicTokenInfo(AuthenticationResult authResult)
{
    TokenInfoText.Text = "";
    if (authResult != null)
    {
        TokenInfoText.Text += $"Username: {authResult.Account.Username}" + Environment.NewLine;
        TokenInfoText.Text += $"Token Expires: {authResult.ExpiresOn.ToLocalTime()}" + Environment.NewLine;
    }
}

詳細情報

Microsoft Graph API の呼び出しに使用するアクセス トークンに加えて、MSAL はユーザーのサインイン後に ID トークンも取得します。 このトークンには、ユーザー関連情報の少量のサブセットが含まれています。 DisplayBasicTokenInfo メソッドは、このトークンに含まれている基本的な情報を表示します。 たとえば、トークンの有効期限やアクセス トークンそのものを表す文字列に加えて、ユーザーの表示名や ID です。 [Call Microsoft Graph API](Microsoft Graph API の呼び出し) ボタンを複数回押すと、後の要求で同じトークンが再利用されてことが確認できます。 また、MSAL がトークンの更新時期だと判断したときに、有効期限が延長されることも確認できます。

コードのテスト

Visual Studio で、お使いのプロジェクトを実行するには、F5 キーを押します。 以下に示すように、アプリケーション MainWindow が表示されます。

Test your application.

初めてアプリケーションを実行して [Call Microsoft Graph API](Microsoft Graph API の呼び出し) ボタンを選択すると、サインインを求められます。 テストを行うには、Microsoft Entra アカウント (職場または学校アカウント) または Microsoft アカウント (live.com、outlook.com) を使用します。

Sign in to the application.

アプリケーションに初めてサインインするときに、次に示すように、アプリケーションがプロファイルにアクセスし、サインインすることを許可することへの同意を求められます。

Provide your consent for application access.

アプリケーションの結果を表示する

サインインしたら、Microsoft Graph API の呼び出しによって返されたユーザー プロファイル情報が表示されます。 結果は、 [API Call Results](API コールの結果) ボックスに表示されます。 AcquireTokenInteractive または AcquireTokenSilent の呼び出しを介して取得されたトークンに関する基本情報は、 [Token Info](トークン情報) ボックスに表示されます。 結果には、以下のプロパティが含まれます。

プロパティ Format 説明
ユーザー名 user@domain.com ユーザーの識別に使用されているユーザー名。
Token Expires DateTime トークンの有効期限が切れる時刻。 MSAL は、必要に応じてトークンを更新することで、有効期限日を延長します。

スコープと委任されたアクセス許可の詳細

Microsoft Graph API には、ユーザーのプロファイルを読み取るための user.read スコープが必要です。 このスコープは、アプリケーション登録ポータルで登録されたすべてのアプリケーションで、既定で自動的に追加されます。 Microsoft Graph の他の API や、バックエンド サーバーのカスタム API には、追加のスコープが必要な場合があります。 Microsoft Graph API には、ユーザーの予定表を表示するための Calendars.Read スコープが必要です。

アプリケーションのコンテキストでユーザーの予定表にアクセスするには、Calendars.Read の委任されたアクセス許可をアプリケーション登録情報に追加します。 次に、Calendars.Read スコープを acquireTokenSilent 呼び出しに追加します。

注意

スコープの数を増やすと、ユーザーは追加の同意を求められることがあります。

ヘルプとサポート

サポートが必要な場合、問題をレポートする場合、またはサポート オプションについて知りたい場合は、開発者向けのヘルプとサポートに関するページを参照してください。

次の手順

保護された Web API を呼び出すデスクトップ アプリをビルドするには、複数のパートで構成される次のシナリオ シリーズを参照してください。