Posted by Tim Bray
Google Play services has started to roll out and should arrive on virtually all Android 2.2+ devices with the Google Play Store in the very near future. At that point, all those will have new tools for working with OAuth 2.0 tokens. This is an example of the kind of agility in rolling out new platform capabilities that Google Play services provides.
Why OAuth 2.0 Matters

The Internet already has too many usernames and passwords, and they donít scale. Furthermore, your Android device has a strong notion of who you are. In this situation, the industry consensus is that OAuth 2.0 is a good choice for the job, offering the promise of strong security minus passwords.
Google Play services make OAuth 2.0 authorization available to Android apps that want to access Google APIs, with a good user experience and security.
Typically, when you want your Android app to use a Google account to access something, you have to pick which account on the device to use, then you have to generate an OAuth 2.0 token, then you have to use it in your HTTP-based dialogue with the resource provider.
These tasks are largely automated for you if youíre using a recent release of the Google APIs Client Library for Java; the discussion here applies if you want to access the machinery directly, for example in sending your own HTTP GETs and POSTs to a RESTful interface.
Preparation

Google Play services has just started rolling out, and even after the rollout is complete, will only be available on compatible Android devices running 2.2 or later. This is the vast majority, but there will be devices out there where itís not available. It is also possible for a user to choose to disable the software.
For these reasons, before you can start making calls, you have to verify that Google Play services is installed. To do this, call isGooglePlayServicesAvailable(). The result codes, and how to deal with them, are documented in the ConnectionResult class.
Choosing an Account

This is not, and has never been, rocket science; there are many examples online that retrieve accounts from Androidís AccountManager and display some sort of pick list. The problem is that they all have their own look and feel, and for something like this, which touches on security, thatís a problem; the user has the right to expect consistency from the system.
Now you can use the handy AccountPicker.newChooseAccountIntent() method to give you an Intent; feed it to startActivityForResult() and youíll launch a nice standardized user experience that will return you an account (if the user feels like providing one).
Two things to note: When youíre talking to these APIs, they require a Google account (AccountManager can handle multiple flavors), so specify GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE argument as the value for the allowableAccountTypes argument. Second, you donít need an android.accounts.Account object, you just use the email-address string (available in account.name) that uniquely identifies it.
Getting a Token

Thereís really only one method call you need to use, GoogleAuthUtil.getToken(). It takes three arguments: a Context, an email address, and another string argument called scope. Every information resource that is willing to talk OAuth 2.0 needs to publish which scope (or scopes) it uses. For example, to access the Google+ API, the scope is oauth2:https://www.googleapis.com/auth/plus.me. You can provide multiple space-separated scopes in one call and get a token that provides access to all of them. Code like this might be typical:

private final static String G_PLUS_SCOPE = "oauth2:https://www.googleapis.com/auth/plus.me"; private final static String USERINFO_SCOPE = "https://www.googleapis.com/auth/userinfo.profile"; private final static String SCOPES = G_PLUS_SCOPE + " " + USERINFO_SCOPE;In an ideal world, getToken() would be synchronous, but three things keep it from being that simple:

  1. The first time an app asks for a token to access some resource, the system will need to interact with the user to make sure theyíre OK with that.
  2. Any time you ask for a token, the system may well have a network conversation with the identity back-end services.
  3. The infrastructure that handles these requests may be heavily loaded and not able to get you your token right away. Rather than keeping you waiting, or just failing, it may ask you to go away and come back a little later.

The first consequence is obvious; you absolutely canít call getToken() on the UI thread, since itís subject to unpredictable delays.
When you call it, the following things can happen:

  • It returns a token. That means that everything went fine, the back-end thinks the authorization was successful, and you should be able to proceed and use the token.
  • It throws a UserRecoverableAuthException, which means that you need to interact with the user, most likely to ask for their approval on using their account for this purpose. The exception has a getIntent() method, whose return value you can feed to startActivityForResult() to take care of that. Of course, youíll need to be watching for the OK in the onActivityResult() method.
  • It throws an IOException, which means that the authorization infrastructure is stressed, or there was a (not terribly uncommon on mobile devices) networking error. You shouldnít give up instantly, because a repeat call might work. On the other hand, if you go back instantly and pester the server again, results are unlikely to be good. So you need to wait a bit; best practice would be the classic exponential-backoff pattern.
  • It throws a GoogleAuthException, which means that authorization just isnít going to happen, and you need to let your user down politely. This can happen if an invalid scope was requested, or the account for the email address doesnít actually exist on the device.

Hereís some sample code:

try { // if this returns, the OAuth framework thinks the token should be usable String token = GoogleAuthUtil.getToken(this, mRequest.email(), mRequest.scope()); response = doGet(token, this); } catch (UserRecoverableAuthException userAuthEx) { // This means that the app hasn't been authorized by the user for access // to the scope, so we're going to have to fire off the (provided) Intent // to arrange for that. But we only want to do this once. Multiple // attempts probably mean the user said no. if (!mSecondTry) { startActivityForResult(userAuthEx.getIntent(), REQUEST_CODE); response = null; } else { response = new Response(-1, null, "Multiple approval attempts"); } } catch (IOException ioEx) { // Something is stressed out; the auth servers are by definition // high-traffic and you can't count on 100% success. But it would be // bad to retry instantly, so back off if (backoff.shouldRetry()) { backoff.backoff(); response = authenticateAndGo(backoff); } else { response = new Response(-1, null, "No response from authorization server."); } } catch (GoogleAuthException fatalAuthEx) { Log.d(TAG, "Fatal Authorization Exception"); response = new Response(-1, null, "Fatal authorization exception: " + fatalAuthEx.getLocalizedMessage()); }This is from a sample library Iíve posted on code.google.com with an AuthorizedActivity class that implements this. We think that some of this authorization behavior is going to be app-specific, so itís not clear that this exact AuthorizedActivity recipe is going to work for everyone; but itís Apache2-licensed, so feel free to use any pieces that work for you. Itís set up as a library project, and thereís also a small sample app called G+ Snowflake that uses it to return some statistics about your Google+ posts; the app is in the Google Play Store and its source is online too.
Registering Your App

Most services that do OAuth 2.0 authorization want you to register your app, and Googleís are no exception. You need to visit the Google APIs Console, create a project, pick the APIs you want to access off the Services menu, and then hit the API Access tab to do the registration. Itíll want you to enter your package name; the value of the package attribute of the manifest element in your AndroidManifest.xml.
Also, itíll want the SHA1 signature of the certificate you used to sign your app. Anyone whoís published apps to Google Play Apps knows about keystores and signing. But before you get there, youíll be working with your debug-version apps, which are signed with a certificate living in ~/.android/debug.keystore (password: ďandroidĒ). Fortunately, your computer probably already has a program called ďkeytoolĒ installed; you can use this to get the signature. For your debug version, a correct incantation is:

keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -v -listThis will print out the SHA1 signature in a nicely labeled easy-to-cut-and-paste form.
This may feel a little klunky, but itís worth it, because some magic is happening. When your app is registered and you generate a token and send it to a service provider, the provider can check with Google, which will confirm that yes, it issued that token, and give the package name of the app it was issued to. Those of you who whoíve done this sort of thing previously will be wondering about Client IDs and API Keys, but with this mechanism you donít need them.
Using Your Token

Suppose youíve registered your app and called GoogleAuthUtil.getToken() and received a token. For the purposes of this discussion, letís suppose that itís ďMissassaugaParnassus42Ē. Then all you need to do is, when you send off an HTTP request to your service provider, include an HTTP header like so:

Authorization: Bearer MissassaugaParnassus42Then your HTTP GETs and POSTs should Just Work. You should call GoogleAuthUtil.getToken() to get a token before each set of GETs or POSTs; itís smart about caching things appropriately, and also about dealing with token expiry and refresh.
Once again, as I said at the top, if youíre happy using the Google APIs Client Library for Java, itíll take care of all the client-side stuff; youíll still need to do the developer console app registration.
Otherwise, thereís a little bit of coding investment here, but the payoff is pretty big: Secure, authenticated, authorized, service access with a good user experience.




More...