| page.title=Authenticating to OAuth2 Services | |
| parent.title=Remembering and Authenticating Users | |
| parent.link=index.html | |
| trainingnavtop=true | |
| previous.title=Remembering Your User | |
| previous.link=identify.html | |
| next.title=Creating a Custom Account Type | |
| next.link=custom_auth.html | |
| @jd:body | |
| <!-- This is the training bar --> | |
| <div id="tb-wrapper"> | |
| <div id="tb"> | |
| <h2>This lesson teaches you to</h2> | |
| <ol> | |
| <li><a href="#Gather">Gather Information</a></li> | |
| <li><a href="#RequestToken">Request an Auth Token</a></li> | |
| <li><a href="#RequestAgain">Request an Auth Token... Again</a></li> | |
| <li><a href="#ConnectToService">Connect to the Online Service</a></li> | |
| </ol> | |
| </div> | |
| </div> | |
| <p>In order to securely access an online service, users need to authenticate to | |
| the service—they need to provide proof of their identity. For an | |
| application that accesses a third-party service, the security problem is even | |
| more complicated. Not only does the user need to be authenticated to access the | |
| service, but the application also needs to be authorized to act on the user's | |
| behalf. </p> | |
| <p>The industry standard way to deal with authentication to third-party services | |
| is the OAuth2 protocol. OAuth2 provides a single value, called an <strong>auth | |
| token</strong>, that represents both the user's identity and the application's | |
| authorization to act on the user's behalf. This lesson demonstrates connecting | |
| to a Google server that supports OAuth2. Although Google services are used as an | |
| example, the techniques demonstrated will work on any service that correctly | |
| supports the OAuth2 protocol.</p> | |
| <p>Using OAuth2 is good for:</p> | |
| <ul> | |
| <li>Getting permission from the user to access an online service using his or | |
| her account.</li> | |
| <li>Authenticating to an online service on behalf of the user.</li> | |
| <li>Handling authentication errors.</li> | |
| </ul> | |
| <h2 id="Gather">Gather Information</h2> | |
| <p>To begin using OAuth2, you need to know a few things about the API you're trying | |
| to access:</p> | |
| <ul> | |
| <li>The url of the service you want to access.</li> | |
| <li>The <strong>auth scope</strong>, which is a string that defines the specific | |
| type of access your app is asking for. For instance, the auth scope for | |
| read-only access to Google Tasks is <code>View your tasks</code>, while the auth | |
| scope for read-write access to Google Tasks is <code>Manage Your | |
| Tasks</code>.</li> | |
| <li>A <strong>client id</strong> and <strong>client secret</strong>, which are | |
| strings that identify your app to the service. You need to obtain these strings | |
| directly from the service owner. Google has a self-service system for obtaining | |
| client ids and secrets. The article <a | |
| href="http://code.google.com/apis/tasks/articles/oauth-and-tasks-on-android.html">Getting | |
| Started with the Tasks API and OAuth 2.0 on Android</a> explains | |
| how to use this system to obtain these values for use with the Google Tasks | |
| API.</li> | |
| </ul> | |
| <h2 id="RequestToken">Request an Auth Token</h2> | |
| <p>Now you're ready to request an auth token. This is a multi-step process.</p> | |
| <img src="{@docRoot}images/training/oauth_dance.png" alt="Procedure for obtaining | |
| a valid auth token from the Android Account Manager"/> | |
| <p>To get an auth token you first need to request the | |
| {@link android.Manifest.permission#ACCOUNT_MANAGER} | |
| to your manifest file. To actually do anything useful with the | |
| token, you'll also need to add the {@link android.Manifest.permission#INTERNET} | |
| permission.</p> | |
| <pre> | |
| <manifest ... > | |
| <uses-permission android:name="android.permission.ACCOUNT_MANAGER" /> | |
| <uses-permission android:name="android.permission.INTERNET" /> | |
| ... | |
| </manifest> | |
| </pre> | |
| <p>Once your app has these permissions set, you can call {@link | |
| android.accounts.AccountManager#getAuthToken AccountManager.getAuthToken()} to get the | |
| token.</p> | |
| <p>Watch out! Calling methods on {@link android.accounts.AccountManager} can be tricky! Since | |
| account operations may involve network communication, most of the {@link | |
| android.accounts.AccountManager} methods are asynchronous. This means that instead of doing all of | |
| your auth work in one function, you need to implement it as a series of callbacks. For example:</p> | |
| <pre> | |
| AccountManager am = AccountManager.get(this); | |
| Bundle options = new Bundle(); | |
| am.getAuthToken( | |
| myAccount_, // Account retrieved using getAccountsByType() | |
| "Manage your tasks", // Auth scope | |
| options, // Authenticator-specific options | |
| this, // Your activity | |
| new OnTokenAcquired(), // Callback called when a token is successfully acquired | |
| new Handler(new OnError())); // Callback called if an error occurs | |
| </pre> | |
| <p>In this example, <code>OnTokenAcquired</code> is a class that implements | |
| {@link android.accounts.AccountManagerCallback}. {@link android.accounts.AccountManager} calls | |
| {@link android.accounts.AccountManagerCallback#run run()} on <code>OnTokenAcquired</code> with an | |
| {@link android.accounts.AccountManagerFuture} that contains a {@link android.os.Bundle}. If | |
| the call succeeded, the token is inside | |
| the {@link android.os.Bundle}.</p> | |
| <p>Here's how you can get the token from the {@link android.os.Bundle}:</p> | |
| <pre> | |
| private class OnTokenAcquired implements AccountManagerCallback<Bundle> { | |
| @Override | |
| public void run(AccountManagerFuture<Bundle> result) { | |
| // Get the result of the operation from the AccountManagerFuture. | |
| Bundle bundle = result.getResult(); | |
| // The token is a named value in the bundle. The name of the value | |
| // is stored in the constant AccountManager.KEY_AUTHTOKEN. | |
| token = bundle.getString(AccountManager.KEY_AUTHTOKEN); | |
| ... | |
| } | |
| } | |
| </pre> | |
| <p>If all goes well, the {@link android.os.Bundle} contains a valid token in the {@link | |
| android.accounts.AccountManager#KEY_AUTHTOKEN} key and you're off to the races. Things don't | |
| always go that smoothly, though...</p> | |
| <h2 id="RequestAgain">Request an Auth Token... Again</h2> | |
| <p>Your first request for an auth token might fail for several reasons:</p> | |
| <ul> | |
| <li>An error in the device or network caused {@link android.accounts.AccountManager} to fail.</li> | |
| <li>The user decided not to grant your app access to the account.</li> | |
| <li>The stored account credentials aren't sufficient to gain access to the account.</li> | |
| <li>The cached auth token has expired.</li> | |
| </ul> | |
| <p>Applications can handle the first two cases trivially, usually by simply | |
| showing an error message to the user. If the network is down or the user decided | |
| not to grant access, there's not much that your application can do about it. The | |
| last two cases are a little more complicated, because well-behaved applications | |
| are expected to handle these failures automatically.</p> | |
| <p>The third failure case, having insufficient credentials, is communicated via the {@link | |
| android.os.Bundle} you receive in your {@link android.accounts.AccountManagerCallback} | |
| (<code>OnTokenAcquired</code> from the previous example). If the {@link android.os.Bundle} includes | |
| an {@link android.content.Intent} in the {@link android.accounts.AccountManager#KEY_INTENT} key, | |
| then the authenticator is telling you that it needs to interact directly with the user before it can | |
| give you a valid token.</p> | |
| <p>There may be many reasons for the authenticator to return an {@link android.content.Intent}. It | |
| may be the first time the user has logged in to this account. Perhaps the user's account has expired | |
| and they need to log in again, or perhaps their stored credentials are incorrect. Maybe the account | |
| requires two-factor authentication or it needs to activate the camera to do a retina scan. It | |
| doesn't really matter what the reason is. If you want a valid token, you're going to have to fire | |
| off the {@link android.content.Intent} to get it.</p> | |
| <pre> | |
| private class OnTokenAcquired implements AccountManagerCallback<Bundle> { | |
| @Override | |
| public void run(AccountManagerFuture<Bundle> result) { | |
| ... | |
| Intent launch = (Intent) result.getResult().get(AccountManager.KEY_INTENT); | |
| if (launch != null) { | |
| startActivityForResult(launch, 0); | |
| return; | |
| } | |
| } | |
| } | |
| </pre> | |
| <p>Note that the example uses {@link android.app.Activity#startActivityForResult | |
| startActivityForResult()}, so that you can capture | |
| the result of the {@link android.content.Intent} by implementing {@link | |
| android.app.Activity#onActivityResult onActivityResult()} in | |
| your own activity. This is important! If you don't capture the result from the | |
| authenticator's response {@link android.content.Intent}, | |
| it's impossible to tell whether the user has successfully authenticated or not. | |
| If the result is {@link android.app.Activity#RESULT_OK}, then the | |
| authenticator has updated the stored credentials so that they are sufficient for | |
| the level of access you requested, and you should call {@link | |
| android.accounts.AccountManager#getAuthToken AccountManager.getAuthToken()} again to request the new | |
| auth token.</p> | |
| <p>The last case, where the token has expired, it is not actually an {@link | |
| android.accounts.AccountManager} failure. The only way to discover whether a token is expired or not | |
| is to contact the server, and it would be wasteful and expensive for {@link | |
| android.accounts.AccountManager} to continually go online to check the state of all of its tokens. | |
| So this is a failure that can only be detected when an application like yours tries to use the auth | |
| token to access an online service.</p> | |
| <h2 id="ConnectToService">Connect to the Online Service</h2> | |
| <p>The example below shows how to connect to a Google server. Since Google uses the | |
| industry standard OAuth2 protocol to | |
| authenticate requests, the techniques discussed here are broadly | |
| applicable. Keep in mind, though, that every | |
| server is different. You may find yourself needing to make minor adjustments to | |
| these instructions to account for your specific | |
| situation.</p> | |
| <p>The Google APIs require you to supply four values with each request: the API | |
| key, the client ID, the client secret, | |
| and the auth key. The first three come from the Google API Console | |
| website. The last is the string value you | |
| obtained by calling {@link android.accounts.AccountManager#getAuthToken(android.accounts.Account,java.lang.String,android.os.Bundle,android.app.Activity,android.accounts.AccountManagerCallback,android.os.Handler) AccountManager.getAuthToken()}. You pass these to the | |
| Google Server as part of | |
| an HTTP request.</p> | |
| <pre> | |
| URL url = new URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=" + <em>your_api_key</em>); | |
| URLConnection conn = (HttpURLConnection) url.openConnection(); | |
| conn.addRequestProperty("client_id", <em>your client id</em>); | |
| conn.addRequestProperty("client_secret", <em>your client secret</em>); | |
| conn.setRequestProperty("Authorization", "OAuth " + token); | |
| </pre> | |
| <p>If the request returns | |
| an HTTP error code of 401, then your token has been denied. As mentioned in the | |
| last section, the most common reason for | |
| this is that the token has expired. The fix is | |
| simple: call | |
| {@link android.accounts.AccountManager#invalidateAuthToken AccountManager.invalidateAuthToken()} and | |
| repeat the token acquisition dance one | |
| more time.</p> | |
| <p>Because expired tokens are such a common occurrence, and fixing them is so easy, many | |
| applications just assume the token has expired before even asking for it. If renewing a token is a | |
| cheap operation for your server, you might prefer to call {@link | |
| android.accounts.AccountManager#invalidateAuthToken AccountManager.invalidateAuthToken()} before the | |
| first call to {@link android.accounts.AccountManager#getAuthToken AccountManager.getAuthToken()}, | |
| and spare yourself the need to request an auth token twice.</p> |