이 문서에서는 AccountsSettingsPane을 사용하여 Windows 웹 계정 관리자 API를 사용하여 UWP(유니버설 Windows 플랫폼) 앱을 Microsoft 또는 Facebook과 같은 외부 ID 공급자에 연결하는 방법을 설명합니다. 사용자의 사용 권한에서 Microsoft 계정을 사용하도록 요청하고, 액세스 토큰을 받고, 이를 사용하여 기본 작업(프로필 데이터 가져오기, OneDrive 계정에 파일 업로드)을 수행하는 방법에 대해 살펴보겠습니다. 이 단계는 웹 계정 관리자를 지원하는 ID 공급자를 사용하여 사용자 권한 및 액세스를 가져오는 것과 흡사합니다.
참고 항목
전체 코드 샘플을 보려면 GitHub의 WebAccountManagement 샘플을 참조하세요.
설정
먼저 Visual Studio에서 비어 있는 새 UWP 앱을 만듭니다.
두번째로, ID 공급자에 연결하려면 앱을 스토어와 연결해야 합니다. 이렇게 하려면 프로젝트를 마우스 오른쪽 단추로 클릭하고 저장/게시>스토어에 앱 연결을 선택하고 마법사의 지침을 따릅니다.
셋째, 간단한 XAML 버튼과 두 개의 텍스트 상자로 구성된 매우 기본적인 UI를 생성합니다.
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Button x:Name="SignInButton" Content="Sign in" Click="SignInButton_Click" />
<TextBlock x:Name="UserIdTextBlock"/>
<TextBlock x:Name="UserNameTextBlock"/>
</StackPanel>
그리고 코드 숨김 버튼에 연결된 이벤트 처리기는 다음과 같습니다:
private void SignInButton_Click(object sender, RoutedEventArgs e)
{
}
마지막으로, 나중에 컴파일 문제에 대해 걱정할 필요가 없도록 다음 네임스페이스를 추가합니다.
using System;
using Windows.Security.Authentication.Web.Core;
using Windows.System;
using Windows.UI.ApplicationSettings;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.Data.Json;
using Windows.UI.Xaml.Navigation;
using Windows.Web.Http;
계정 설정 창 표시
시스템은 ID 공급자 및 AccountsSettingsPane이라고 하는 웹 계정을 관리하기 위한 기본 제공 사용자 인터페이스를 제공합니다. 다음과 같이 보여줄 수 있습니다:
private void SignInButton_Click(object sender, RoutedEventArgs e)
{
AccountsSettingsPane.Show();
}
앱을 실행하고 "로그인" 단추를 클릭하면 빈 창이 표시됩니다.
시스템에서 UI 셸만 제공하므로 창은 비어 있습니다. 개발자는 프로그래밍 방식으로 창을 ID 공급자로 채웁니다.
팁
선택적으로 IAsyncAction을 반환하는 Show 대신 ShowAddAccountAsync를 사용하여 작업 상태를 쿼리할 수 있습니다.
AccountCommandsRequested에 등록
창에 명령을 추가하려면 먼저 AccountCommandsRequested 이벤트 처리기를 등록합니다. 이렇게 하면 사용자가 창을 표시하도록 요청할 때(예: XAML 단추 클릭) 시스템은 빌드 논리를 실행하라는 지시를 받게 됩니다.
당신의 코드 숨김 파일에서 OnNavigatedTo 및 OnNavigatedFrom 이벤트를 재정의하고, 다음 코드를 추가하세요.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
AccountsSettingsPane.GetForCurrentView().AccountCommandsRequested += BuildPaneAsync;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
AccountsSettingsPane.GetForCurrentView().AccountCommandsRequested -= BuildPaneAsync;
}
사용자는 계정과 자주 상호 작용하지 않으므로 이렇게 이벤트 처리기를 등록하고 등록 취소하는 방식은 메모리 누수 방지에 도움을 줍니다. 이러한 방식으로 사용자 지정된 창은 사용자가 요청할 가능성이 높은 경우에만 메모리에 있습니다(예: "설정" 또는 "로그인" 페이지에 있기 때문).
계정 설정 창 빌드
AccountsSettingsPane이 표시되면 BuildPaneAsync 메서드가 호출됩니다. 이곳이 창에 표시된 명령들에 사용자 지정 코드를 배치하는 곳입니다.
먼저 지연을 얻는것으로 부터 시작합니다. 이렇게 하면 빌드가 완료될 때까지 AccountsSettingsPane 표시를 지연하라고 시스템에 지시됩니다.
private async void BuildPaneAsync(AccountsSettingsPane s,
AccountsSettingsPaneCommandsRequestedEventArgs e)
{
var deferral = e.GetDeferral();
deferral.Complete();
}
다음으로 WebAuthenticationCoreManager.FindAccountProviderAsync 메서드를 사용하여 공급자를 가져옵니다. 공급자의 URL은 공급자에 따라 다르며 공급자의 자료에서 찾을 수 있습니다. Microsoft 계정 및 Microsoft Entra의 경우 "https://login.microsoft.com"입니다.
private async void BuildPaneAsync(AccountsSettingsPane s,
AccountsSettingsPaneCommandsRequestedEventArgs e)
{
var deferral = e.GetDeferral();
var msaProvider = await WebAuthenticationCoreManager.FindAccountProviderAsync(
"https://login.microsoft.com", "consumers");
deferral.Complete();
}
또한 "consumers" 문자열을 선택적 권한 매개 변수에 전달합니다. 이는 Microsoft가 "소비자"를 위한 MSA(Microsoft 계정)와 "조직"용 Microsoft Entra라는 두 가지 유형의 인증을 제공하기 때문입니다. "consumers" 권한은 우리가 MSA 옵션을 원한다는 것을 나타냅니다. 기업용 엔터프라이즈 앱을 개발하는 경우 대신 "organizations" 스트링을 사용합니다.
마지막으로 다음과 같은 새 WebAccountProviderCommand를 만들어 AccountsSettingsPane에 공급자를 추가합니다.
private async void BuildPaneAsync(AccountsSettingsPane s,
AccountsSettingsPaneCommandsRequestedEventArgs e)
{
var deferral = e.GetDeferral();
var msaProvider = await WebAuthenticationCoreManager.FindAccountProviderAsync(
"https://login.microsoft.com", "consumers");
var command = new WebAccountProviderCommand(msaProvider, GetMsaTokenAsync);
e.WebAccountProviderCommands.Add(command);
deferral.Complete();
}
새 WebAccountProviderCommand에 전달한 GetMsaTokenAsync 메서드는 아직 존재하지 않으므로(다음 단계에서 빌드할 예정) 지금은 빈 메서드로 자유롭게 추가할 수 있습니다.
위의 코드를 실행하면 다음과 같은 창이 표시됩니다.
토큰 요청
AccountsSettingsPane에 Microsoft 계정 옵션이 표시되면 사용자가 선택할 때 어떤 결과가 나타날지 처리해야 합니다. 사용자가 Microsoft 계정으로 로그인하도록 선택할 때 GetMsaTokenAsync 메서드를 실행하도록 등록했으므로 여기서 토큰을 가져옵니다.
토큰을 가져오려면 다음과 같이 RequestTokenAsync 메서드를 사용합니다.
private async void GetMsaTokenAsync(WebAccountProviderCommand command)
{
WebTokenRequest request = new WebTokenRequest(command.WebAccountProvider, "wl.basic");
WebTokenRequestResult result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
}
이 예제에서는 문자열 “wl.basic”을 scope 매개 변수에 전달합니다. 범위는 특정 사용자에 대한 서비스 제공에서 기인하여 요청된 정보의 유형을 대변합니다. 특정 범위에서는 이름 및 메일 주소와 같은 사용자의 기본 정보에만 액세스할 수 있지만 다른 범위에서는 사용자의 사진이나 메일 받은 편지함과 같은 중요한 정보에 대한 액세스를 허용할 수 있습니다. 일반적으로 앱은 해당 기능을 수행하는 데 필요한 최소 허용 범위를 사용해야 합니다. 서비스 공급자는 서비스에 사용할 토큰을 가져오는 데 필요한 범위에 대한 설명서를 제공합니다.
- Microsoft 365 및 Outlook.com 범위의 경우 Outlook REST API(버전 2.0) 사용을 참조하세요.
- OneDrive 범위의 경우 OneDrive 인증 및 로그인을 참조하세요.
팁
앱에서 로그인 힌트(기본 전자 메일 주소로 사용자 필드 채우기) 또는 로그인 환경과 관련된 기타 특수 속성을 사용하는 경우 선택적으로 WebTokenRequest.AppProperties 속성에 나열할 수 있습니다. 이렇게 하면 시스템에서 웹 계정을 캐싱할 때 속성을 무시하므로 캐시의 계정 불일치가 방지됩니다.
엔터프라이즈 앱을 개발하는 경우 Microsoft Entra 인스턴스에 연결하고 일반 MSA 서비스 대신 Microsoft Graph API를 사용할 수 있습니다. 이 시나리오에서는 대신 다음 코드를 사용합니다:
private async void GetAadTokenAsync(WebAccountProviderCommand command)
{
string clientId = "your_guid_here"; // Obtain your clientId from the Azure Portal
WebTokenRequest request = new WebTokenRequest(provider, "User.Read", clientId);
request.Properties.Add("resource", "https://graph.microsoft.com");
WebTokenRequestResult result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
}
이 문서의 나머지 부분도 MSA 시나리오를 계속 설명하지만 Microsoft Entra에 대한 코드는 매우 유사합니다. GitHub의 전체 샘플을 포함하여 Entra/Graph에 대한 자세한 내용은 Microsoft Graph 설명서를 참조하세요.
토큰 사용
RequestTokenAsync 메서드는 요청 결과를 포함하는 WebTokenRequestResult 개체를 반환합니다. 요청이 성공적이였다면 토큰이 들어있습니다.
private async void GetMsaTokenAsync(WebAccountProviderCommand command)
{
WebTokenRequest request = new WebTokenRequest(command.WebAccountProvider, "wl.basic");
WebTokenRequestResult result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
if (result.ResponseStatus == WebTokenRequestStatus.Success)
{
string token = result.ResponseData[0].Token;
}
}
참고 항목
토큰을 요청할 때 오류가 발생하면 1단계에서 설명한 대로 앱을 스토어에 연결했는지 확인합니다. 이 단계를 건너뛰었다면 앱에서 토큰을 확보할 수 없습니다.
토큰이 있으면 이를 사용하여 공급자의 API를 호출하는 것이 가능합니다. 아래 코드에서는 Microsoft 365 API 사용자 정보를 호출하여 사용자에 대한 기본 정보를 가져와서 UI에 표시합니다. 그러나 대부분의 경우 이렇게 얻은 토큰을 저장한 다음, 이를 다른 메서드에 사용하는 것이 좋습니다.
private async void GetMsaTokenAsync(WebAccountProviderCommand command)
{
WebTokenRequest request = new WebTokenRequest(command.WebAccountProvider, "wl.basic");
WebTokenRequestResult result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
if (result.ResponseStatus == WebTokenRequestStatus.Success)
{
string token = result.ResponseData[0].Token;
var restApi = new Uri(@"https://apis.live.net/v5.0/me?access_token=" + token);
using (var client = new HttpClient())
{
var infoResult = await client.GetAsync(restApi);
string content = await infoResult.Content.ReadAsStringAsync();
var jsonObject = JsonObject.Parse(content);
string id = jsonObject["id"].GetString();
string name = jsonObject["name"].GetString();
UserIdTextBlock.Text = "Id: " + id;
UserNameTextBlock.Text = "Name: " + name;
}
}
}
다양한 REST API를 호출하는 방법은 공급자마다 다릅니다. 토큰을 사용하는 방법에 대한 자세한 내용은 공급자의 API 설명서를 참조하세요.
나중을 위해 계정 저장
토큰은 사용자에 대한 정보를 즉시 얻는것엔 유용하지만 일반적으로 수명의 편차가 큽니다. 예를 들어 MSA 토큰은 몇 시간 동안만 유효합니다. 다행히 토큰이 만료될 때마다 AccountsSettingsPane을 다시 표시할 필요가 없습니다. 사용자가 앱에 권한을 한 번 부여하면 나중에 사용할 수 있는 사용자의 계정 정보를 저장할 수 있습니다.
이렇게 하려면 WebAccount 클래스를 사용합니다. WebAccount는 토큰을 요청하는 데 사용한 동일한 메서드에 의해 반환됩니다.
private async void GetMsaTokenAsync(WebAccountProviderCommand command)
{
var request = new WebTokenRequest(command.WebAccountProvider, "wl.basic");
WebTokenRequestResult result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
if (result.ResponseStatus == WebTokenRequestStatus.Success)
{
WebAccount account = result.ResponseData[0].WebAccount;
}
}
WebAccount 인스턴스가 있으면 쉽게 저장할 수 있습니다. 다음 예제에서는 LocalSettings를 사용합니다. LocalSettings 및 기타 방법을 사용하여 사용자 데이터를 저장하는 방법에 대한 자세한 내용은 앱 설정 및 데이터 저장 및 검색을 참조하세요.
private async void StoreWebAccount(WebAccount account)
{
ApplicationData.Current.LocalSettings.Values["CurrentUserProviderId"] = account.WebAccountProvider.Id;
ApplicationData.Current.LocalSettings.Values["CurrentUserId"] = account.Id;
}
그런 다음 다음과 같은 비동기 메서드를 사용하여 저장된 WebAccount를 사용하여 백그라운드에서 토큰을 가져오려고 시도할 수 있습니다.
private async Task<string> GetTokenSilentlyAsync()
{
string providerId = ApplicationData.Current.LocalSettings.Values["CurrentUserProviderId"]?.ToString();
string accountId = ApplicationData.Current.LocalSettings.Values["CurrentUserId"]?.ToString();
if (null == providerId || null == accountId)
{
return null;
}
WebAccountProvider provider = await WebAuthenticationCoreManager.FindAccountProviderAsync(providerId);
WebAccount account = await WebAuthenticationCoreManager.FindAccountAsync(provider, accountId);
var request = new WebTokenRequest(provider, "wl.basic");
WebTokenRequestResult result = await WebAuthenticationCoreManager.GetTokenSilentlyAsync(request, account);
if (result.ResponseStatus == WebTokenRequestStatus.UserInteractionRequired)
{
// Unable to get a token silently - you'll need to show the UI
return null;
}
else if (result.ResponseStatus == WebTokenRequestStatus.Success)
{
// Success
return result.ResponseData[0].Token;
}
else
{
// Other error
return null;
}
}
AccountsSettingsPane을 빌드하는 코드 바로 앞에 이전 메서드를 배치합니다. 토큰을 백그라운드에서 가져온 경우 창을 표시할 필요는 없습니다.
private async void SignInButton_Click(object sender, RoutedEventArgs e)
{
string silentToken = await GetMsaTokenSilentlyAsync();
if (silentToken != null)
{
// the token was obtained. store a reference to it or do something with it here.
}
else
{
// the token could not be obtained silently. Show the AccountsSettingsPane
AccountsSettingsPane.Show();
}
}
토큰을 조용히 가져오는 것은 매우 간단하기 때문에 이 프로세스를 사용하여 기존 토큰을 캐싱하는 대신 세션 간에 토큰을 새로고침 하는것을 추천 합니다(토큰 만료 가능성 때문).
참고 항목
위 예제는 기본적인 성공 및 실패 사례만 제공합니다. 또한 앱은 흔하지 않은 시나리오(예: 사용자가 앱의 사용 권한을 취소하거나 Windows에서 계정을 제거하는 경우 등)를 고려하고 적절하게 처리해야 합니다.
저장된 계정 제거
웹 계정을 유지하는 경우 사용자가 자신의 계정을 앱과 분리하는 것을 허용하려고 할 수 있습니다. 이렇게 하면 앱에서 효과적으로 "로그아웃"할 수 있습니다. 시작 시 계정 정보가 더 이상 자동으로 로드되지 않습니다. 이렇게 하려면 먼저 스토리지에서 저장된 계정 및 공급자 정보를 제거합니다. 그런 다음 SignOutAsync 를 호출하여 캐시를 지우고 앱에 있을 수 있는 기존 토큰을 무효화합니다.
private async Task SignOutAccountAsync(WebAccount account)
{
ApplicationData.Current.LocalSettings.Values.Remove("CurrentUserProviderId");
ApplicationData.Current.LocalSettings.Values.Remove("CurrentUserId");
account.SignOutAsync();
}
WebAccountManager를 지원하지 않는 공급자 추가하기
서비스의 인증을 앱에 통합하려고 하지만 해당 서비스가 WebAccountManager (예: Instagram 또는 X/Twitter)를 지원하지 않는 경우 해당 공급자를 AccountsSettingsPane에 수동으로 추가할 수 있습니다. 이렇게 하려면 새 WebAccountProvider 개체를 만들고 고유한 이름과 .png 아이콘을 제공한 다음 WebAccountProviderCommands 목록에 추가합니다. 다음은 몇 가지 스텁 코드입니다.
private async void BuildPaneAsync(AccountsSettingsPane s, AccountsSettingsPaneCommandsRequestedEventArgs e)
{
// other code here
var twitterProvider = new WebAccountProvider("twitter", "Twitter", new Uri(@"ms-appx:///Assets/twitter-auth-icon.png"));
var twitterCmd = new WebAccountProviderCommand(twitterProvider, GetTwitterTokenAsync);
e.WebAccountProviderCommands.Add(twitterCmd);
// other code here
}
private async void GetTwitterTokenAsync(WebAccountProviderCommand command)
{
// Manually handle Twitter sign-in here
}
참고 항목
그러면 AccountsSettingsPane 에 아이콘만 추가되고 아이콘을 클릭할 때 지정한 메서드를 실행합니다(이 경우 GetTwitterTokenAsync). 실제 인증을 처리하는 코드를 제공해야 합니다. 자세한 내용은 REST 서비스를 사용하여 인증하기 위한 도우미 메서드를 제공하는 웹 인증 브로커를 참조하세요.
사용자 지정 헤더 추가하기
다음과 같이 HeaderText 속성을 사용하여 계정 설정 창을 사용자 지정할 수 있습니다.
private async void BuildPaneAsync(AccountsSettingsPane s, AccountsSettingsPaneCommandsRequestedEventArgs e)
{
// other code here
args.HeaderText = "MyAwesomeApp works best if you're signed in.";
// other code here
}
머리글 텍스트에 너무 열정적이지 마세요. 굷고 짧게 만드세요. 로그인 프로세스가 복잡하고 추가 정보를 표시해야 하는 경우 사용자 지정 링크를 사용하여 사용자를 별도의 페이지에 연결합니다.
사용자 지정 링크 추가
지원되는 WebAccountProviders 아래에 링크로 표시되는 AccountsSettingsPane에 사용자 지정 명령을 추가할 수 있습니다. 사용자 지정 명령은 개인 정보 취급 방침을 표시하거나 어려움을 겪고 있는 사용자를 위한 지원 페이지를 로드하는 등 사용자 계정과 관련된 간단한 작업에 안성맞춤입니다.
예를 들어 다음과 같습니다.
private async void BuildPaneAsync(AccountsSettingsPane s, AccountsSettingsPaneCommandsRequestedEventArgs e)
{
// other code here
var settingsCmd = new SettingsCommand(
"settings_privacy",
"Privacy policy",
async (x) => await Launcher.LaunchUriAsync(new Uri(@"https://privacy.microsoft.com/en-US/")));
e.Commands.Add(settingsCmd);
// other code here
}
이론적으로 설정 명령은 어떤 상황에서도 사용할 수 있습니다. 그러나 위에서 설명한 것과 같은 직관적인 계정 관련 시나리오로 사용을 제한하는 것을 추천합니다.