Enterprise Integration Zone is brought to you in partnership with:

My name is Pieter De Rycke, I am a Belgian technical .Net architect working in the insurance sector. I am always eager to learn new technologies and I like to keep up with the latest innovations and trends in the IT sector. I am a big believer in web services for interoperability between platforms. Pieter is a DZone MVB and is not an employee of DZone and has posted 34 posts at DZone. You can read more from them at their website. View Full User Profile

Google OAuth 2.0 on Windows Phone

03.13.2013
| 3214 views |
  • submit to reddit

Introduction

OAuth is a protocol for authorization. It allows desktop, mobile and web applications to access web resources (mostly REST services) on behalf of a user. The protocol permits this without the user having to share its credentials (typically, a username and password pair) with the application. OAuth is a widely implemented protocol. Various companies (like Facebook, Twitter, Google, Microsoft, Dropbox …) use it to protect their APIs.

In this article, I will explain how we can implement support for Google OAuth in a Windows Phone application.

How does OAuth work

In a nutshell when developing a mobile application, a developer must register its application to the vendor of the service (in our case Google) who will assign a clientId and a clientSecret to the application.

The login flow starts with the application opening an embedded web browser control. This web control must load a specific Google login page. In the query string, the clientId of the application and the requested scopes are sent to the login page. Scopes are the actions the application wants to perform on behalf of the user. Google will handle the user authentication and consent, but at the end an authorization code is sent to the web browser control (using the “title” of the final HTML page).

OAuth Windows Phone authorization flow

After having received the authorization code, the application can exchange this for an access token and a refresh token. The access token can be used by the application to perform the necessary operations on behalf of the user. The refresh token must be stored for future use; it allows to request a new access token when the previous one expired.

Implementing Google OAuth on Windows Phone

You will have to register your application at https://code.google.com/apis/console#access. This will provide you a clientId and clientSecret that you can use to identity your application to the user when performing the OAuth flow.

My implementation consists of 2 big components, a class called “OAuthAuthorization” and a Windows Phone Page called “LogingPage”.

When the developer calls the method “Authorize” of the OAuthAuthorization class, the login page is opened on the screen. This page will show the embedded web browser control full screen (an important detail is that scripting support for this embedded must be activated; by default it is disabled). The Google login page is opened and navigated event handler is attached to the browser control. When the web browser control is redirected to the success page of the login flow, the authorization code is extracted and the login page is closed.

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();
        }
    }
}

Using the client id and the client secret, the OAuthAuthorization class will call a Google REST API to exchange the authorization code for an access token and a refresh token.

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);
            }
        }
    }
}

Using the OAuth implementation

The implemented framework is simple in usage. The following example shows how to use it:

// 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);

The access token must be sent using the Authorization: Bearer HTTP header when calling a Google API.

Conclusion

Implementing OAuth support on Windows Phone is quite simple to do. The authentication and user consent logic is handled by Google and integrated into an app by embedding the web browser control. Once a token is obtained, the application can perform the requested actions on behalf of the user until the user revokes the access for the application.

I have put the source code on GitHub as a reusable framework. The OAuth framework should also be usable with the various other OAuth providers.

You can find it at: https://github.com/pieterderycke/MobileOAuth.



Published at DZone with permission of Pieter De Rycke, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)