Здравствуйте!
Одной из удобных функций Windows Azure Mobile Services является использования внешних служб аутентификаций, которые предоставляются Google, Facebook, Twitter и сам Microsoft Account. Это означает, что разработчику, нет необходимости имплементировать работу с каждым из них отдельно, для этого есть унифицированные методы.
Среди официально поддерживаемых платформ числится почти все кроме… обычной Windows Desktop.
О чём речь
В официальной документации по аутентификации довольно подобно расписан метод использования этого функционала, однако, приведённый в нём примеру завязан на платформозависимый Windows.UI.Popups. Неужели такая мелочь может удержать нас от использования этого мощного инструмента в разработке для привычной среды, которая к тому же, все ещё доминирует? Давайте разбираться.
Постановка задачи
Отображение модального окна необходимо для:
- Отображения Web-страницы внешнего провайдера
- Получение user id от внешнего провайдера для идентификации пользователя и authentication token от Azure — который необходим для дальнейших запросов к бакенду.
- Возврат User ID в экземпляр класса MobileServiceUser, а authentication token в экземпляр класса MobileServiceClient.
Лично я противник модальных окон, поэтому всё сделаем в едином окне, с применением асинхронных методов.
Реализация задумки
Перед тем как начать, вы должны зарегистрировать Ваше приложение у себя в Microsoft Account для получения кода клиента и секретного ключа, именно Microsoft-провайдер я буду использовать для примера.
В вёрстке нам понадобятся:
- WPF окошко с Frame, в который мы будем грузить нужные нам страницы.
- Простую страничку «LoginPage» c кнопкой «Login».
- Вторая страница — просто контейнер для WebBrowser компонента — «WebLoginPage».
В коде реализуем метод-расширения для класса MobileServiceClient:
public static class DesktopWebLoginExtension { public async Task<MobileServiceUser> LoginAsync(this MobileServiceClient client, MobileServiceAuthenticationProvider provider, LoginPape loginPage) { Uri startUri = new Uri(client.ApplicationUri, "login/" + provider.ToString().ToLowerInvariant()); Uri endUri = new Uri(client.ApplicationUri, "login/done"); LoginResult externalAuthProviderLoginResult = await loginPage.GetLoginResultAsync(startUri, endUri); if (externalAuthProviderLoginResult == null) { return null; } var returnServiceUser = new MobileServiceUser(externalAuthProviderLoginResult.UserId); returnServiceUser.MobileServiceAuthenticationToken = externalAuthProviderLoginResult.AuthenticationToken; client.CurrentUser = returnServiceUser; return returnServiceUser; } }
Разберём:
- Как дополнительный параметр наш метод принимает loginPage — что конечно является грубым нарушением software layer, но для примера сойдет.
- startUri и endUri собраны исходя из этой документации.
- loginPage.GetLoginResultAsync — асинхронный метод, который вернёт нам экземпляр класса LoginResult
- Далее происходит заполнение необходимых нам полей для дальнейшей работы.
А вот и сам класс-контейнер, в последствии он будет заполнен из JSON-ответа:
public class LoginResult { public string UserId { get; set; } public string AuthenticationToken { get; set; } }
Код страницы WebLoginPage выглядит вот так:
public class WebLoginPage : Page { private Uri startUri; private Uri endUri; public delegate void LoginCompleteEventHandler(LoginResult result); public event LoginCompleteEventHandler UserLogedIn; public LoginBrowserPage() { InitializeComponent(); webBrowser.Navigating += webBrowser_Navigating; } public Task<LoginResult> GetLoginResultAsync(Uri startUri, Uri endUri) { this.startUri = startUri; this.endUri = endUri; var returnTask = new TaskCompletionSource<LoginResult>(); webBrowser.Navigate(startUri); this.UserLogedIn += (r) => { returnTask.SetResult(r); }; return returnTask.Task; } internal void webBrowser_Navigating(object sender, NavigatingCancelEventArgs e) { if (e.Uri.Equals(this.endUri)) { string uri = e.Uri.ToString(); if (uri.LastIndexOf("#token=") != 0) { var startOfToken = uri.IndexOf("#token=") + "#token=".Length; uri = uri.Substring(startOfToken).Replace("%2C", ","); JObject jsonObj = JObject.Parse(uri); var userId = jsonObj["user"]["userId"].ToObject<string>(); var authToken = jsonObj["authenticationToken"].ToObject<string>(); UserLogedIn(new LoginResult() { UserId = userId, AuthenticationToken = authToken }); } else { UserLogedIn(null); } } } }
Разберём:
- Мы объявили событие, которое будет возникать по завершении аутентификации в нашем мини браузере.
- GetLoginResultAsync — метод, который «ждёт» событие завершения процесса авторизации с помощью замечательного класса TaskCompletionSource. Так мы добились единой «линии ожидания» из асинхронных методов. С большой натяжкой это можно назвать модальной загрузкой страницы во фрейм с возвратом результата в вызывающую функцию (асинхронно) — как и советует нам действовать Microsoft с приходом асинхронных методов.
- webBrowser_Navigating — в этом обработчике события мы старательно десериализуем JSON-ответ от сервиса и получаем заветные UserId и AuthenticationToken.
- Вызываем заветное событие — после чего все асинхронные вызовы завершаются и вызывающий метод получает результат аутентификации.
Теперь ничего не мешает нам в нужном месте написать:
var user = await MobileServiceClient.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, webLoginPage);
P.S. При завершении наш webBrowser предложит нам скачать JSON ответ от сервиса. Это проблема IE движка решается вот таким ключиком в реестре:
Windows Registry Editor Version 5.00;
[HKEY_CLASSES_ROOT\MIME\Database\Content Type\application/json]
«CLSID»="{25336920-03F9-11cf-8FD0-00AA00686F13}"
«Encoding»=hex:08,00,00,00
Вывод
С помощью выше переведённого кода мы сняли неприятное ограничением связанное с невозможностью просто вызвать MobileService.LoginAsync в десктопном приложении и получить результат аутентификации асинхронно.
Спасибо за внимание.
Ссылки:
Windows Azure Mobile Services REST API Reference
Get started with authentication in Mobile Services
Register your application in Google
ссылка на оригинал статьи http://habrahabr.ru/post/201668/
Добавить комментарий