Вступление
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 .
