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 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 as 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",
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:
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 tying 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 are 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
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.
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"))
{
webBrowser1.Visibility = Visibility.Collapsed;
e.Cancel = true;
int pos = e.Uri.Query.IndexOf("=");
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;
}
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