Click here to Skip to main content
11,645,033 members (64,274 online)
Click here to Skip to main content

Google OAuth2 on Windows Phone

, 30 Jan 2012 CPOL 29.5K 1.4K 25
Rate this:
Please Sign up or sign in to vote.
An example of using Google API's and OAuth2 authentication on windows phone 7

Introduction

Google exposes a rich set of RESTful APIs that allow apps to interact with their applications, services and data. While Google supports a number of authentication and authorization mechanisms, they recommend using OAuth2, so since I thought I'd explore how to make that work on WP7.

If you're like me as you approach consuming Google services, you'll read up on OAuth2, go over some example code and hope you can find something to just drop into your app and move on the the interesting stuff. While there is plenty of documentation out there on OAuth2 and examples of how to use it, even a .NET library from Google, there are a few subtleties that need to be built into an app to deal with a number of different use cases. I haven't found any detailed explanation of some of those or good approaches to build them into an app in such a way that they are well encapsulated.

  • OAuth2 for an installed application is a multi-step process. The app doesn't capture a username and password from the user but rather displays an embedded web page for authentication and authorization. On success the app then receives a code which must be exchanged for an access token.
  • Authentication happens synchronously but the communication is asynchronous in nature.
  • The access token comes with an expiration. When it expires it can be refreshed without requiring the user to re authenticate but this does require the app to make the refresh call to Google.
  • The access token can be serialized so that new instances of the application don't need to re-authenticate.
  • The user can revoke permission for the app to access Google services on their behalf at anytime completely outside of the application environment.

My goal with this code was to first understand OAuth2 and Google's use of it, but also to create some code that hides the details of when and how authorization happens from the rest of application. Because the need to re-authenticate or refresh the access token can happen at any time I wanted to make it is a natural as possible for application code to use RESTful services without needing to know about those details.

All this example application does is allow the user to authenticate with Google, authorize the app to access their basic profile data and then display the profile data of the authenticated user. It does however demonstrate the basics of OAuth2 and hopefully present some examples of how to implement it within a WP7 app.

Background

First off, if you've never used a Google API it's good to have a brief understanding of how their services work and a basic understanding of their OAuth2 installed application implementation. If you're going to create an app that uses a Google service you need register it with their API Console. That's where you get the ClientId and secret which are referenced below as well as gain permission for your app to use specific Google services.

The attached code uses:

You can get all of those packages via NuGet. *as of this writing the latest version of RestSharp (102.6) doesn't work with the latest version of JSON.NET (4.0.7) so you may need to make sure to get JSON.NET 4.05)

Using the code

NOTE

In order to run the demo you'll need to register with Google code and get a client id and secret key which you can then enter in the AuthenticationViewModel constructor.
#warning PUT YOUR APP SPECIFIC STUFF HERE
            _process = new AuthenticationProcess()
            {
                ClientId = "YOUR APP CLIENT ID",
                Secret = "YOUR APP SECRET",

                // this specifies which Google APIs your app 
                // intends to use and needs permission for
                Scope = "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"
            };

The OAuth2 Sequence

The basic OAuth2 sequence is a series of request/responses between the user, the app and Google:

nativeflow.png

As you can see the user doesn't actually interact with your application in order to login and authorize it. The user interacts directly with Google but still your app needs to know the result of the interaction in order to proceed.

In this example app, which is a MVVM app, the logic of tieing authentication to the app is with the AuthenticationViewModel class. The communication back and forth to Google is within the AuthenticationProcess class.

One thing you'll notice is that nowhere in the code (outside of the auth view model) will you see code kicking off the login process. The only thing any other view model has to make sure to do is asynchronously get the access code from the auth view model. As long as it does this it can assume that the apps is authenticated, or at least will be when its callback is invoked.

This solves a problem that I've had in other Rest client apps: controlling the entry point such that authentication happens correctly at the right time and making sure that client access is aware of the current authentication state.

OAuth2 In This Example

Since this is an MVVM Light app, it comes with a ViewModelLocator, a MainPage and a MainViewModel. The MainPage is what gets navigated to at application start and part of its UI binds to a property on the MainViewModel named Profile that holds the user's Google profile data. When the UI binds to that property the MainViewModel leads the profile data in the process retrieving the access token from the AuthenticationViewModel. In this example the auth view model is passed to the MainViewModel in its constructor and is the _authProvider member.

public Profile Profile
{
    get
    {
        if(_profile == null)
            _authProvider.GetAccessCode(s => LoadProfile(s));

        return _profile;
    }
    set
    {
        if (_profile != value)
        {
            _profile = value;
            RaisePropertyChanged("Profile");
        }
    }
}                

The call into AuthenticationViewModel::GetAccessCode is where the logic of authentication, authorization, refresh etc is encapsulated.

private Queue<Action<string>> _queuedRequests = new Queue<Action<string>>();
public void GetAccessCode(Action<string> callback)
{
    lock (_sync)
    {
        if (_isAuthenticating)
        {
            _queuedRequests.Enqueue(callback);
        }
        else if (HasAuthenticated)
        {
            if (!_process.AuthResult.IsExpired)
            {
                callback(_process.AuthResult.access_token);
            }
            else
            {
                _isAuthenticating = true;
                _queuedRequests.Enqueue(callback);
                _process.RefreshAccessToken();
            }
        }
        else
        {
            _isAuthenticating = true;
            _queuedRequests.Enqueue(callback);

            ((PhoneApplicationFrame)App.Current.RootVisual).Navigate(new Uri("/AuthenticationPage.xaml", UriKind.Relative));
            AuthUri = _process.AuthUri;
        }
    }
}

There's a number of things going on in that method:

  • First if an authentication is already in process, queue the call back and return
  • If the user is authenticated and the access token is valid, just invoke the callback
  • If the user is authenticated but the access token is expired, queue the callback and refresh the token
  • If the user hasn't authenticated successfully, queue the callback and navigate to the authentication page

Following the flow in the final block, which will be the case on the first launch of the app, the user will be navigated to the authentication page that display a webrowser pointed at the AuthUri property of the auth view model.

The AuthUri is the Google page where the user logs in and will look something like this https://accounts.google.com/o/oauth2/auth?response_type=code&redirect_uri=http://localhost&scope=https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile&client_id=YYYY

login.PNG

After authenticating with Google, they will be asked to authorize your app to access a defined set of APIs, as defined respectively by the client_id and scope arguments in the above Uri.

authorize.PNG

After the user presses "Allow access" Google will redirect the browser to the redirect_uri that we passed in the starting address. When the AutenticationPage codebehind sees the web browser going to the host specified in the redirect_uri argument of the original address, we know that Google is passing us back an access code which we can then exchange for an access token. The access code is part of the query string of the redirect address. We don't ever actually navigate to the redirect_uri but rather use it as sentinel value so we know when the auth is successful.

    private void webBrowser1_Navigating(object sender, NavigatingEventArgs e)
    {
        if (e.Uri.Host.Equals("localhost")) // in our case we used localhost as the redirect_uri
        {
            webBrowser1.Visibility = Visibility.Collapsed;
            e.Cancel = true;
            int pos = e.Uri.Query.IndexOf("=");

            // setting this ui element text will bind it back to the view model
            codeBlock.Text = pos > -1 ? e.Uri.Query.Substring(pos + 1) : null;
        }
    }

Once the code is sent back to the auth view model (in this case because we have a hidden textblock with two way binding) the view model exchanges it for an access token:

    private string _code;
    public string Code
    {
        get
        {
            return _code;
        }
        set
        {
            _code = value;
            _process.ExchangeCodeForToken(Code);
        }
    }
 
    ...

    class AuthenticationProcess
    {
        public void ExchangeCodeForToken(string code)
        {
            if (string.IsNullOrEmpty(code))
            {
                OnAuthenticationFailed();
            }
            else
            {
                var request = new RestRequest(this.TokenEndPoint, Method.POST);
                request.AddParameter("code", code);
                request.AddParameter("client_id", this.ClientId);
                request.AddParameter("client_secret", this.Secret);
                request.AddParameter("redirect_uri", "http://localhost");
                request.AddParameter("grant_type", "authorization_code");

                client.ExecuteAsync<AuthResult>(request, GetAccessToken);
            }
        }

        void GetAccessToken(IRestResponse<AuthResult> response)
        {
            if (response == null || response.StatusCode != HttpStatusCode.OK
                || response.Data == null || string.IsNullOrEmpty(response.Data.access_token))
            {
                OnAuthenticationFailed();
            }
            else
            {
                Debug.Assert(response.Data != null);
                AuthResult = response.Data;
                OnAuthenticated();
            }
        }

    }

At which point the AuthenticationProcess class signals to the auth view model that authentication is successful, the view model invokes any queued callbacks, tidies up a bit and the process is complete.

    void _process_Authenticated(object sender, EventArgs e)
    {
        _isAuthenticating = false;

        while (_queuedRequests.Count > 0)
            _queuedRequests.Dequeue()(_process.AuthResult.access_token);

        ViewModelLocator.SaveSetting("auth", _process.AuthResult);        

        RaisePropertyChanged("HasAuthenticated");
    }

And then the callback, all the way back on the MainViewModel, is invoked with a valid access token and we can move on with doing what we came here to do: invoke a Google API passing the access token to a RestSharp authenticator

    private void LoadProfile(string access_token)
    {
        Debug.WriteLine("loading profile");

        RestClient client = new RestClient("https://www.googleapis.com");
        client.Authenticator = new OAuth2AuthorizationRequestHeaderAuthenticator(access_token);
        var request = new RestRequest("/oauth2/v1/userinfo", Method.GET);
        client.ExecuteAsync<Profile>(request, ProfileLoaded);
    }
    private void ProfileLoaded(IRestResponse<Profile> response)
    {
        Profile = response.Data;
    }
profile.PNG

A similar sequence exists when the access token needs to be refreshed but without the need for a UI.

Conclusion

I can certainly see the advantage of OAuth2 from a security perspective. At no time does that app have the user's credentials. The entire exchange happens between the user and Google. No password is stored on the client and never does the app pass a password to the Google API.

It does make things more complicated than the simple, ask the user for credentials and include them in every call approach but hopefully this article provides an example of how to begin building around OAuth2.

History

  • 1/29/2012 - Initial upload

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Don Kackman
Team Leader Starkey Laboratories
United States United States
The first computer program I ever wrote was in BASIC on a TRS-80 Model I and it looked something like:
10 PRINT "Don is cool"
20 GOTO 10
It only went downhill from there.

Hey look, I've got a blog

You may also be interested in...

Comments and Discussions

 
QuestionI getting Error after give user name and password Pin
Vijaydhas15-Sep-14 23:33
memberVijaydhas15-Sep-14 23:33 
AnswerRe: I getting Error after give user name and password Pin
Don Kackman16-Sep-14 3:00
memberDon Kackman16-Sep-14 3:00 
GeneralRe: I getting Error after give user name and password Pin
Vijaydhas8-Oct-14 0:14
memberVijaydhas8-Oct-14 0:14 
GeneralRe: I getting Error after give user name and password Pin
Vijaydhas8-Oct-14 1:35
memberVijaydhas8-Oct-14 1:35 
QuestionCan we add feature to fetch user contact list Pin
raj804226-Aug-13 1:04
memberraj804226-Aug-13 1:04 
AnswerRe: Can we add feature to fetch user contact list Pin
Don Kackman26-Aug-13 9:08
memberDon Kackman26-Aug-13 9:08 
GeneralMy vote of 5 Pin
Kanasz Robert21-Sep-12 1:38
mvpKanasz Robert21-Sep-12 1:38 
QuestionMainPage - Cannot create an instance of "ViewModelLocator" Pin
tony oldfield8-May-12 20:14
membertony oldfield8-May-12 20:14 
AnswerRe: MainPage - Cannot create an instance of "ViewModelLocator" Pin
Don Kackman10-May-12 7:02
memberDon Kackman10-May-12 7:02 
GeneralTimely article Pin
Member 86976877-May-12 7:22
memberMember 86976877-May-12 7:22 
QuestionProblem with the code - help needed! Pin
gong00182-Mar-12 21:15
membergong00182-Mar-12 21:15 
AnswerRe: Problem with the code - help needed! Pin
Don Kackman3-Mar-12 4:43
memberDon Kackman3-Mar-12 4:43 
GeneralRe: Problem with the code - help needed! Pin
gong00183-Mar-12 17:55
membergong00183-Mar-12 17:55 
GeneralRe: Problem with the code - help needed! Pin
Don Kackman4-Mar-12 8:18
memberDon Kackman4-Mar-12 8:18 
GeneralRe: Problem with the code - help needed! Pin
gong00185-Mar-12 21:17
membergong00185-Mar-12 21:17 
QuestionNice Pin
Sacha Barber7-Feb-12 4:44
mvpSacha Barber7-Feb-12 4:44 
QuestionSpring.NET Social Pin
MaRuXeLo7930-Jan-12 12:12
memberMaRuXeLo7930-Jan-12 12:12 
QuestionRunning the app Pin
Don Kackman30-Jan-12 4:07
memberDon Kackman30-Jan-12 4:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150731.1 | Last Updated 30 Jan 2012
Article Copyright 2012 by Don Kackman
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid