教學課程:在 Windows Presentation Foundation (WPF) 傳統型應用程式中登入使用者並呼叫 Microsoft Graph

在本教學課程中,您將建置原生 Windows Desktop .NET (XAML) 應用程式來登入使用者,並取得存取令牌以呼叫 Microsoft Graph API。

當您完成本指南時,您的應用程式將能夠呼叫使用個人帳戶的受保護 API(包括 outlook.com、live.com 和其他帳戶)。 應用程式也會使用任何使用 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 Graph API 或 Web API,以接受來自 Microsoft 身分識別平台 端點的令牌。 在此案例中,您會透過授權標頭將令牌新增至 HTTP 要求。 Microsoft 驗證連結庫 (MSAL) 會處理令牌取得和更新。

處理令牌擷取以存取受保護的Web API

驗證用戶之後,範例應用程式會收到令牌,您可以用來查詢 Microsoft Graph API 或受 Microsoft 身分識別平台 保護的 Web API。

Microsoft Graph 之類的 API 需要令牌,才能允許存取特定資源。 例如,需要令牌才能讀取使用者的配置檔、存取使用者的行事曆或傳送電子郵件。 您的應用程式可以使用 MSAL 來指定 API 範圍來存取這些資源,以要求存取令牌。 接著,此存取令牌會針對針對受保護資源所做的每個呼叫新增至 HTTP 授權標頭。

MSAL 會為您管理快取和重新整理存取令牌,讓您的應用程式不需要。

NuGet 套件

本指南使用下列 NuGet 套件:

程式庫 描述
Microsoft.Identity.Client Microsoft 驗證連結庫 (MSAL.NET)

設定您的專案

在本節中,您將建立新的專案,以示範如何將 Windows Desktop .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. 流覽至 [身分>識別應用程式> 應用程式註冊]。
  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. 將應用程式類別更新為下列專案:

    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方法會處理令牌擷取和更新,而不需要任何用戶互動。 第一次執行之後 AcquireTokenInteractiveAcquireTokenSilent 是用來取得令牌的一般方法來存取受保護資源的後續呼叫,因為對要求或更新令牌的呼叫會以無訊息方式進行。

最後, AcquireTokenSilent 方法可能會失敗。 失敗的原因可能是使用者已在另一個裝置上註銷或變更其密碼。 當 MSAL 偵測到問題可藉由要求互動式動作來解決時,就會引發 MsalUiRequiredException 例外狀況。 您的應用程式可以透過兩種方式來處理此例外狀況:

  • 它可以立即撥打電話 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 授權標頭中新增取得的令牌。 在此範例中,資源是 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 支援可同時登入多個帳戶的案例。 例如,使用者有多個帳戶的電子郵件應用程式。

顯示基本令牌資訊

若要顯示令牌的基本資訊,請將下列方法新增至 您的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 也會取得標識符令牌。 此令牌包含與用戶相關的一小部分資訊。 方法 DisplayBasicTokenInfo 會顯示令牌中包含的基本資訊。 例如,它會顯示使用者的顯示名稱和標識符,以及代表存取令牌本身的令牌到期日和字串。 您可以多次選取 [ 呼叫 Microsoft Graph API] 按鈕,並查看後續要求重複使用相同的令牌。 您也可以在 MSAL 決定要更新令牌的時間時,看到到期日正在延長。

測試您的程式碼

若要執行您的專案,請在 Visual Studio 中選取 F5您的應用程式 MainWindow 隨即顯示,如下所示:

Test your application.

第一次執行應用程式並選取 [呼叫 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 呼叫結果 ] 方塊中。 透過呼叫 AcquireTokenInteractiveAcquireTokenSilent 取得之令牌的基本信息應該會顯示在 [令牌資訊] 方塊中。 結果包含下列屬性:

屬性 格式 描述
使用者名稱 user@domain.com 用來識別用戶的用戶名稱。
令牌到期 Datetime 令牌到期的時間。 MSAL 會視需要更新令牌,以延長到期日。

範圍和委派許可權的詳細資訊

Microsoft Graph API 需要 user.read 範圍才能讀取使用者配置檔。 這個範圍預設會在應用程式註冊入口網站中註冊的每個應用程式中自動新增。 Microsoft Graph 的其他 API,以及後端伺服器的自定義 API,可能需要其他範圍。 Microsoft Graph API 需要 Calendars.Read 範圍來列出使用者的行事曆。

若要在應用程式的內容中存取使用者的行事曆,請將 Calendars.Read 委派的許可權新增至應用程式註冊資訊。 然後,將 Calendars.Read 範圍新增至 acquireTokenSilent 呼叫。

注意

當您增加範圍數目時,可能會提示使用者輸入其他同意。

說明與支援 

如果您需要協助、想要回報問題,或想要了解支援選項,請參閱 開發人員的說明和支援。

下一步

深入瞭解在多部分案例系列中建置可呼叫受保護 Web API 的桌面應用程式: