Статьи

Google OAuth 2.0 на Windows Phone

Вступление

OAuth — это протокол для авторизации. Он позволяет настольным, мобильным и веб-приложениям получать доступ к веб-ресурсам (в основном, к сервисам REST) ​​от имени пользователя. Протокол разрешает это без необходимости предоставления пользователю доступа к своим учетным данным (как правило, к имени пользователя и паролю) с приложением. OAuth — широко распространенный протокол. Различные компании (такие как Facebook, Twitter, Google, Microsoft, Dropbox…) используют его для защиты своих API.

В этой статье я объясню, как мы можем реализовать поддержку Google OAuth в приложении Windows Phone.

Как работает OAuth

В двух словах, при разработке мобильного приложения разработчик должен зарегистрировать свое приложение у поставщика сервиса (в нашем случае, Google), который назначит clientId и clientSecret для приложения.

Процесс входа в систему начинается с открытия приложением встроенного элемента управления веб-браузера. Этот веб-элемент управления должен загружать определенную страницу входа в Google. В строке запроса clientId приложения и запрошенные области отправляются на страницу входа. Области действия — это действия, которые приложение хочет выполнить от имени пользователя. Google будет обрабатывать аутентификацию и согласие пользователя, но в конце код авторизации отправляется в элемент управления веб-браузера (с использованием «заголовка» последней HTML-страницы).

OAuth Windows Phone поток авторизации

После получения кода авторизации приложение может обменять его на токен доступа и токен обновления. Токен доступа может использоваться приложением для выполнения необходимых операций от имени пользователя. Токен обновления должен быть сохранен для использования в будущем; это позволяет запросить новый токен доступа, когда истек срок действия предыдущего.

Реализация 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 .