웹 계정 관리자

이 문서에서는 Windows 10 및 Windows 11 웹 계정 관리자를 사용하여 AccountsSettingsPane에서 UWP(유니버설 Windows 플랫폼) 앱을 외부 ID 공급자(예: Microsoft, Facebook)에 연결하는 방법에 대해 설명합니다. 사용자의 사용 권한에서 Microsoft 계정을 사용하도록 요청하고, 액세스 토큰을 받고, 이를 사용하여 기본 작업(프로필 데이터 가져오기, OneDrive 계정에 파일 업로드)을 수행하는 방법에 대해 살펴보겠습니다. 이 단계는 웹 계정 관리자를 지원하는 ID 공급자를 사용하여 사용자 권한 및 액세스를 가져오는 것과 흡사합니다.

참고 항목

전체 코드 샘플을 보려면 GitHub의 WebAccountManagement 샘플을 참조하세요.

설정

우선, Visual Studio에서 새 빈 앱을 생성하세요.

두번째로, ID 공급자에 연결하려면 앱을 스토어와 연결해야 합니다. 이렇게 하려면 프로젝트를 마우스 오른쪽 단추로 클릭하고 저장/게시>스토어에 앱 연결을 선택하고 마법사의 지침을 따릅니다.

셋째, 간단한 XAML 버튼과 두 개의 텍스트 상자로 구성된 매우 기본적인 UI를 생성합니다.

<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
	<Button x:Name="LoginButton" Content="Log in" Click="LoginButton_Click" />
	<TextBlock x:Name="UserIdTextBlock"/>
	<TextBlock x:Name="UserNameTextBlock"/>
</StackPanel>

그리고 코드 숨김 버튼에 연결된 이벤트 처리기는 다음과 같습니다:

private void LoginButton_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 LoginButton_Click(object sender, RoutedEventArgs e)
{
	AccountsSettingsPane.Show(); 
}

앱을 실행하면 "로그인" 버튼을 클릭하세요, 빈 창이 표시될 겁니다.

Screenshot of the Choose an account window with no accounts listed.

시스템에서는 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 계정 및 Azure Active Directory의 경우 해당 URL은“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가 "consumers"를 위한 MSA(Microsoft 계정)와 "조직"에 대한 AAD(Azure Active Directory)라는 두 가지 유형의 인증을 제공하기 때문입니다. "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에 전달할 GetMsaToken 메서드가 아직 없으므로(다음 단계에서 빌드) 일단 빈 메서드에 추가하면 됩니다.

위의 코드를 실행하면 다음과 같은 창이 표시됩니다.

Screenshot of the Choose an account window with accounts listed.

토큰 요청

AccountsSettingsPane에 Microsoft 계정 옵션이 표시되면 사용자가 선택할 때 어떤 결과가 나타날지 처리해야 합니다. 사용자가 Microsoft 계정 로그인을 선택하면 GetMsaToken 메서드가 실행되도록 등록했기 때문에 여기서 토큰을 확보합니다.

토큰을 확보하려면 다음과 같이 RequestTokenAsync 메서드를 사용합니다:

private async void GetMsaTokenAsync(WebAccountProviderCommand command)
{
	WebTokenRequest request = new WebTokenRequest(command.WebAccountProvider, "wl.basic");
	WebTokenRequestResult result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
}

이 예제에서는 문자열 “wl.basic”을 scope 매개 변수에 전달합니다. 범위는 특정 사용자에 대한 서비스 제공에서 기인하여 요청된 정보의 유형을 대변합니다. 특정 범위에서는 이름 및 메일 주소와 같은 사용자의 기본 정보에만 액세스할 수 있지만 다른 범위에서는 사용자의 사진이나 메일 받은 편지함과 같은 중요한 정보에 대한 액세스를 허용할 수 있습니다. 일반적으로 앱은 해당 기능을 수행하는 데 필요한 최소 허용 범위를 사용해야 합니다. 서비스 공급자는 서비스에 사용할 토큰을 가져오는 데 필요한 범위에 대한 설명서를 제공합니다.

필요에 따라 앱이 로그인 힌트(기본 메일 주소로 사용자 필드를 채우기 위해) 또는 로그인 환경과 관련된 기타 특수 속성을 사용하는 경우 WebTokenRequest.AppProperties 속성에 나열하세요. 이렇게 하면 시스템에서 웹 계정을 캐시할 때 속성을 무시하여 캐시의 계정 불일치를 방지합니다.

엔터프라이즈 앱을 개발하는 경우 AAD(Azure Active Directory) 인스턴스에 연결하는 것과 일반 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 시나리오를 계속 설명하지만, AAD 코드는 매우 유사성을 지닙니다. GitHub의 전체 샘플을 포함한 AAD/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를 호출하는 것이 가능합니다. 아래 코드에서는 user info Microsoft Live 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)
{
	WebTokenRequest 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);

	WebTokenRequest 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 void LoginButton_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(예: Google+ 또는 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 login 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
}

Screenshot of the Choose an account window with no accounts listed and a message that says My Awesome App works best if you're signed in.

머리글 텍스트에 너무 열정적이지 마세요. 굷고 짧게 만드세요. 로그인 프로세스가 복잡하고 추가 정보를 표시해야 하는 경우 사용자를 별도의 페이지에 연결하는 개인 맞춤 링크를 사용하세요.

지원되는 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
}

Screenshot of the Choose an account window with no accounts listed and a link to a Privacy policy.

이론적으로 설정 명령은 어떤 상황에서도 사용할 수 있습니다. 그러나 위에서 설명한 것과 같은 직관적인 계정 관련 시나리오로 사용을 제한하는 것을 추천합니다.

참고 항목

Windows.Security.Authentication.Web.Core 네임스페이스

Windows.Security.Credentials 네임스페이스

AccountsSettingsPane 클래스

웹 인증 브로커

웹 계정 관리 샘플

Lunch Scheduler 앱