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",
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 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
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