자습서: WPF(Windows Presentation Foundation) 데스크톱 앱에서 사용자 로그인 및 Microsoft Graph 호출

이 자습서에서는 사용자를 로그인시키고 Microsoft Graph API를 호출하는 액세스 토큰을 가져오는 네이티브 Windows Desktop .NET(XAML) 앱을 빌드합니다.

이 가이드를 완료하면 애플리케이션은 개인 계정(outlook.com, live.com 등 포함)을 사용하는 보호된 API를 호출할 수 있습니다. 또한 애플리케이션은 Microsoft Entra ID를 사용하는 모든 회사 또는 조직의 회사 및 학교 계정을 사용합니다.

이 자습서에서:

  • Visual Studio에서 WPF(Windows Presentation Foundation) 프로젝트 만들기
  • .NET용 MSAL(Microsoft 인증 라이브러리) 설치
  • 응용 프로그램 등록
  • 사용자 로그인 및 로그아웃을 지원하는 코드 추가
  • Microsoft Graph API를 호출하는 코드 추가
  • 앱 테스트

필수 조건

이 가이드에서 생성된 샘플 앱의 작동 원리

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

이 가이드를 사용하여 만든 샘플 애플리케이션을 사용하면 Windows 데스크톱 애플리케이션이 Microsoft Graph API를 쿼리하거나 웹 API가 Microsoft ID 플랫폼 엔드포인트에서 토큰을 수락할 수 있습니다. 이 시나리오에서는 인증 헤더를 통해 HTTP 요청에 토큰을 추가합니다. MSAL(Microsoft 인증 라이브러리)에서는 토큰 획득 및 갱신을 처리합니다.

보호되는 웹 API에 액세스하기 위한 토큰 획득 처리

사용자가 인증된 후에 샘플 애플리케이션은 Microsoft ID 플랫폼으로 보호되는 Microsoft Graph API 또는 웹 API를 쿼리하는 데 사용할 수 있는 토큰을 수신합니다.

Microsoft Graph와 같은 API에는 특정 리소스에 대한 액세스를 허용하는 토큰이 필요합니다. 예를 들어 사용자 프로필을 읽고, 사용자 일정에 액세스하고, 메일을 보내는 데 토큰이 필요합니다. 애플리케이션에서는 MSAL을 사용하여 액세스 토큰을 요청하고 API 범위를 지정하여 이러한 리소스에 액세스할 수 있습니다. 그런 다음 이 액세스 토큰은 보호되는 리소스에 대한 모든 호출에 사용될 수 있도록 HTTP 인증 헤더에 추가됩니다.

MSAL은 사용자를 대신해 액세스 토큰 캐싱 및 새로 고침을 관리하므로 애플리케이션에서 이러한 작업을 수행할 필요가 없습니다.

NuGet 패키지

이 가이드에서는 다음 NuGet 패키지를 사용합니다.

라이브러리 설명
Microsoft.Identity.Client Microsoft 인증 라이브러리(MSAL.NET)

프로젝트 설정

이 섹션에서는 토큰이 필요한 웹 API를 쿼리할 수 있도록 Windows Desktop .NET 애플리케이션(XAML)을 Microsoft에 로그인과 통합하는 방법을 설명하기 위해 새 프로젝트를 만듭니다.

만들 애플리케이션에는 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. 앱 클래스를 다음으로 업데이트합니다.

    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이 대화형 작업을 요구해 이 문제를 해결할 수 있다고 감지하면 MsalUiRequiredException 예외를 발생합니다. 애플리케이션에서는 이러한 예외를 다음 두 가지 방법으로 처리할 수 있습니다.

  • 즉시 AcquireTokenInteractive에 대해 호출할 수 있습니다. 이 호출로 인해 사용자에게 로그인하라는 메시지가 표시됩니다. 이 패턴은 사용자가 사용할 수 있는 오프라인 콘텐츠가 없는 온라인 애플리케이션에서 사용됩니다. 이 설정을 통해 생성된 샘플은 이 패턴을 따릅니다. 이 패턴은 샘플을 처음 실행할 때 작동되는 것을 확인할 수 있습니다.

  • 이 애플리케이션을 사용한 사용자가 없기 때문에 PublicClientApp.Users.FirstOrDefault()에는 null 값이 포함되며 MsalUiRequiredException 예외가 throw됩니다.

  • 샘플의 코드는 AcquireTokenInteractive를 호출하여 예외를 처리합니다. 그러면 사용자에게 로그인하라는 메시지가 표시됩니다.

  • 로그인에 적절한 시기를 선택할 수 있도록 대화형 로그인이 필요하다는 시각적 표시를 사용자에게 표시할 수 있습니다. 또는 애플리케이션이 나중에 AcquireTokenSilent를 다시 시도할 수 있습니다. 사용자가 중단 없이 다른 애플리케이션 기능을 사용할 수 있는 경우에 이 패턴이 종종 사용됩니다. 예를 들어 애플리케이션에서 오프라인 콘텐츠를 사용할 수 있는 경우입니다. 이 경우에 사용자는 보호된 리소스에 액세스하거나, 오래된 정보를 새로 고치기 위해 로그인하는 시점을 결정할 수 있습니다. 또는 네트워크가 일시적으로 사용할 수 없게 된 후에 복원된 경우 애플리케이션이 AcquireTokenSilent를 다시 시도하도록 결정할 수 있습니다.

방금 가져온 토큰을 사용하여 Microsoft Graph API를 호출합니다.

MainWindow.xaml.cs에 다음과 같은 새 메서드를 추가합니다. 이 메서드는 인증 헤더를 사용하여 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 호출에 관한 추가 정보

이 애플리케이션 예제에서는 토큰이 필요한 보호되는 리소스에 대한 HTTP GET 요청을 실행한 다음, 호출자에게 콘텐츠를 반환하는 데 GetHttpContentWithToken 메서드를 사용합니다. 이 메서드는 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;
    }
}

자세한 정보

또한 MSAL에서는 사용자가 로그인한 후에 Microsoft Graph API를 호출하는 데 사용되는 액세스 토큰을 외에도 ID 토큰을 가져옵니다. 이 토큰에는 사용자에게 관련된 정보의 작은 하위 집합이 포함됩니다. DisplayBasicTokenInfo 메서드는 토큰에 포함된 기본 정보를 표시합니다. 예를 들어, 토큰 만료 날짜 및 액세스 토큰 자체를 나타내는 문자열뿐만 아니라 사용자 표시 이름 및 ID을 표시합니다. Microsoft Graph API 호출 (Call Microsoft Graph API) 단추를 여러 번 선택하면 동일한 토큰이 후속 요청에 다시 사용된 것을 볼 수 있습니다. MSAL이 토큰 갱신 시점이라고 판단한 경우에는 만료 날짜가 연장된 것도 확인할 수 있습니다.

코드 테스트

F5를 선택하여 Visual Studio에서 프로젝트를 실행합니다. 애플리케이션 MainWindow는 다음과 같이 표시됩니다.

Test your application.

처음으로 애플리케이션을 실행하여 Call 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 호출 결과 상자에 표시됩니다. AcquireTokenInteractive 또는 AcquireTokenSilent에 대한 호출을 통해 획득한 토큰에 관한 기본 정보는 토큰 정보 상자에 표시되어야 합니다. 결과에 다음 속성이 포함될 수 있습니다.

속성 서식 설명
사용자 이름 user@domain.com 사용자를 식별하는 데 사용하는 사용자 이름입니다.
토큰 만료 DateTime 토큰이 만료되는 시간입니다. MSAL은 필요에 따라 토큰을 갱신하여 만료 날짜를 연장합니다.

범위 및 위임된 권한에 대한 자세한 내용

Microsoft Graph API는 user.read 범위가 있어야만 사용자 프로필을 읽을 수 있습니다. 이 범위는 애플리케이션 등록 포털에서 등록된 모든 애플리케이션에서 기본적으로 자동 추가됩니다. 다른 Microsoft Graph용 API와 백 엔드 서버용 사용자 지정 API에는 추가 범위가 필요할 수 있습니다. Microsoft Graph API는 Calendars.Read 범위가 있어야만 사용자 일정을 나열할 수 있습니다.

애플리케이션의 컨텍스트에서 사용자 일정에 액세스하려면 애플리케이션 등록 정보에 Calendars.Read 위임 권한을 추가합니다. 그런 다음 acquireTokenSilent 호출에 Calendars.Read 범위를 추가합니다.

참고 항목

범위 수를 늘리면 사용자에게 추가 동의를 요청하는 메시지가 표시될 수 있습니다.

도움말 및 지원 

도움이 필요하거나, 문제를 보고하거나, 지원 옵션에 대해 알아보려면 개발자를 위한 도움말 및 지원을 참조하세요.

다음 단계

여러 부분으로 구성된 시나리오 시리즈에서 보호된 웹 API를 호출하는 데스크톱 앱을 빌드하는 방법에 대해 자세히 알아보세요.