Вступление
OAuth — это протокол для авторизации. Он позволяет настольным, мобильным и веб-приложениям получать доступ к веб-ресурсам (в основном, к сервисам REST) от имени пользователя. Протокол разрешает это без необходимости предоставления пользователю доступа к своим учетным данным (как правило, к имени пользователя и паролю) с приложением. OAuth — широко распространенный протокол. Различные компании (такие как Facebook, Twitter, Google, Microsoft, Dropbox…) используют его для защиты своих API.
В этой статье я объясню, как мы можем реализовать поддержку Google OAuth в приложении Windows Phone.
Как работает OAuth
В двух словах, при разработке мобильного приложения разработчик должен зарегистрировать свое приложение у поставщика сервиса (в нашем случае, Google), который назначит clientId и clientSecret для приложения.
Процесс входа в систему начинается с открытия приложением встроенного элемента управления веб-браузера. Этот веб-элемент управления должен загружать определенную страницу входа в Google. В строке запроса clientId приложения и запрошенные области отправляются на страницу входа. Области действия — это действия, которые приложение хочет выполнить от имени пользователя. Google будет обрабатывать аутентификацию и согласие пользователя, но в конце код авторизации отправляется в элемент управления веб-браузера (с использованием «заголовка» последней HTML-страницы).
После получения кода авторизации приложение может обменять его на токен доступа и токен обновления. Токен доступа может использоваться приложением для выполнения необходимых операций от имени пользователя. Токен обновления должен быть сохранен для использования в будущем; это позволяет запросить новый токен доступа, когда истек срок действия предыдущего.
Реализация Google OAuth на Windows Phone
Вам нужно будет зарегистрировать свое приложение по адресу https://code.google.com/apis/console#access . Это предоставит вам clientId и clientSecret, которые вы можете использовать для идентификации вашего приложения для пользователя при выполнении потока OAuth.
Моя реализация состоит из 2 больших компонентов, класса «OAuthAuthorization» и страницы Windows Phone под названием «LogingPage».
Когда разработчик вызывает метод «Авторизация» класса OAuthAuthorization, страница входа открывается на экране. На этой странице будет отображаться полный экран управления встроенным веб-браузером (важная деталь в том, что поддержка сценариев для этого встроенного должна быть активирована; по умолчанию она отключена). Страница входа в Google открывается, и обработчик перемещаемых событий присоединяется к элементу управления браузера. Когда элемент управления веб-браузера перенаправляется на страницу успеха потока входа в систему, код авторизации извлекается и страница входа в систему закрывается.
public partial class LoginPage : PhoneApplicationPage { public LoginPage() { InitializeComponent(); } protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); IDictionary parameters = this.NavigationContext.QueryString; string authEndpoint = parameters["authEndpoint"]; string clientId = parameters["clientId"]; string scope = parameters["scope"]; string uri = string.Format("{0}?response_type=code&client_id={1}&redirect_uri={2}&scope={3}", authEndpoint, clientId, "urn:ietf:wg:oauth:2.0:oob", scope); webBrowser.Navigate(new Uri(uri, UriKind.Absolute)); } private void webBrowser_Navigated(object sender, NavigationEventArgs e) { string title = (string)webBrowser.InvokeScript("eval", "document.title.toString()"); if (title.StartsWith("Success")) { string authorizationCode = title.Substring(title.IndexOf('=') + 1); PhoneApplicationService.Current.State["MobileOauth.AuthorizationCode"] = authorizationCode; NavigationService.GoBack(); } } }
Используя идентификатор клиента и секрет клиента, класс OAuthAuthorization вызовет API Google REST для обмена кодом авторизации на токен доступа и токен обновления.
public class OAuthAuthorization { private readonly string authEndpoint; private readonly string tokenEndpoint; private readonly PhoneApplicationFrame frame; public OAuthAuthorization(string authEndpoint, string tokenEndpoint) { this.authEndpoint = authEndpoint; this.tokenEndpoint = tokenEndpoint; this.frame = (PhoneApplicationFrame)(Application.Current.RootVisual); } public async Task Authorize(string clientId, string clientSecret, IEnumerable scopes) { string uri = string.Format("/MobileOauth;component/LoginPage.xaml?authEndpoint={0}&clientId={1}&scope={2}", authEndpoint, clientId, string.Join(" ", scopes)); SemaphoreSlim semaphore = new SemaphoreSlim(0, 1); Observable.FromEvent( h => new NavigatingCancelEventHandler(h), h => this.frame.Navigating += h, h => this.frame.Navigating -= h) .SkipWhile(h => h.EventArgs.NavigationMode != NavigationMode.Back) .Take(1) .Subscribe(e => semaphore.Release()); frame.Navigate(new Uri(uri, UriKind.Relative)); await semaphore.WaitAsync(); string authorizationCode = (string)PhoneApplicationService.Current.State["MobileOauth.AuthorizationCode"]; return await RequestAccessToken(authorizationCode, clientId, clientSecret); } public async Task RefreshAccessToken(string clientId, string clientSecret, string refreshToken) { HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(tokenEndpoint); httpRequest.Method = "POST"; httpRequest.ContentType = "application/x-www-form-urlencoded"; using (Stream stream = await httpRequest.GetRequestStreamAsync()) { using (StreamWriter writer = new StreamWriter(stream)) { writer.Write("refresh_token=" + Uri.EscapeDataString(refreshToken) + "&"); writer.Write("client_id=" + Uri.EscapeDataString(clientId) + "&"); writer.Write("client_secret=" + Uri.EscapeDataString(clientSecret) + "&"); writer.Write("grant_type=refresh_token"); } } using (WebResponse response = await httpRequest.GetResponseAsync()) { using (StreamReader streamReader = new StreamReader(response.GetResponseStream())) { string result = streamReader.ReadToEnd(); TokenPair tokenPair = JsonConvert.DeserializeObject(result); tokenPair.RefreshToken = refreshToken; return tokenPair; } } } private async Task RequestAccessToken(string authorizationCode, string clientId, string clientSecret) { HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(tokenEndpoint); httpRequest.Method = "POST"; httpRequest.ContentType = "application/x-www-form-urlencoded"; using (Stream stream = await httpRequest.GetRequestStreamAsync()) { using (StreamWriter writer = new StreamWriter(stream)) { writer.Write("code=" + Uri.EscapeDataString(authorizationCode) + "&"); writer.Write("client_id=" + Uri.EscapeDataString(clientId) + "&"); writer.Write("client_secret=" + Uri.EscapeDataString(clientSecret) + "&"); writer.Write("redirect_uri=" + Uri.EscapeDataString("urn:ietf:wg:oauth:2.0:oob") + "&"); writer.Write("grant_type=authorization_code"); } } using (WebResponse response = await httpRequest.GetResponseAsync()) { using (StreamReader streamReader = new StreamReader(response.GetResponseStream())) { string result = streamReader.ReadToEnd(); return JsonConvert.DeserializeObject(result); } } } }
Использование реализации OAuth
Реализованный фреймворк прост в использовании. В следующем примере показано, как его использовать:
// Request an access token OAuthAuthorization authorization = new OAuthAuthorization( "https://accounts.google.com/o/oauth2/auth", "https://accounts.google.com/o/oauth2/token"); TokenPair tokenPair = await authorization.Authorize( ClientId, ClientSecret, new string[] {GoogleScopes.CloudPrint, GoogleScopes.Gmail}); // Request a new access token using the refresh token (when the access token was expired) TokenPair refreshTokenPair = await authorization.RefreshAccessToken( ClientId, ClientSecret, tokenPair.RefreshToken);
Маркер доступа должен быть отправлен с использованием HTTP-заголовка Authorization: Bearer при вызове Google API.
Вывод
Реализовать поддержку OAuth на Windows Phone довольно просто. Логика аутентификации и согласия пользователя обрабатывается Google и интегрируется в приложение с помощью встроенного элемента управления веб-браузера. Как только токен получен, приложение может выполнять запрошенные действия от имени пользователя, пока пользователь не аннулирует доступ к приложению.
Я поместил исходный код на GitHub в качестве фреймворка многократного использования. Платформа OAuth также должна использоваться с другими поставщиками OAuth.
Вы можете найти его по адресу: https://github.com/pieterderycke/MobileOAuth .