Home | History | Annotate | Download | only in id-auth
      1 page.title=Authenticating to OAuth2 Services
      2 parent.title=Remembering and Authenticating Users
      3 parent.link=index.html
      4 
      5 trainingnavtop=true
      6 previous.title=Remembering Your User
      7 previous.link=identify.html
      8 next.title=Creating a Custom Account Type
      9 next.link=custom_auth.html
     10 
     11 @jd:body
     12 
     13     <!-- This is the training bar -->
     14 <div id="tb-wrapper">
     15   <div id="tb">
     16 <h2>This lesson teaches you to</h2>
     17 <ol>
     18   <li><a href="#Gather">Gather Information</a></li>
     19   <li><a href="#RequestToken">Request an Auth Token</a></li>
     20   <li><a href="#RequestAgain">Request an Auth Token... Again</a></li>
     21   <li><a href="#ConnectToService">Connect to the Online Service</a></li>
     22 </ol>
     23   </div>
     24 </div>
     25 
     26 <p>In order to securely access an online service, users need to authenticate to
     27 the service&mdash;they need to provide proof of their identity. For an
     28 application that accesses a third-party service, the security problem is even
     29 more complicated. Not only does the user need to be authenticated to access the
     30 service, but the application also needs to be authorized to act on the user's
     31 behalf. </p>
     32 
     33 <p>The industry standard way to deal with authentication to third-party services
     34 is the OAuth2 protocol. OAuth2 provides a single value, called an <strong>auth
     35 token</strong>, that represents both the user's identity and the application's
     36 authorization to act on the user's behalf. This lesson demonstrates connecting
     37 to a Google server that supports OAuth2. Although Google services are used as an
     38 example, the techniques demonstrated will work on any service that correctly
     39 supports the OAuth2 protocol.</p>
     40 
     41 <p>Using OAuth2 is good for:</p>
     42 <ul>
     43 <li>Getting permission from the user to access an online service using his or
     44 her account.</li>
     45 <li>Authenticating to an online service on behalf of the user.</li>
     46 <li>Handling authentication errors.</li>
     47 </ul>
     48 
     49 
     50 <h2 id="Gather">Gather Information</h2>
     51 
     52 <p>To begin using OAuth2, you need to know a few things about the API you're trying
     53 to access:</p>
     54 
     55 <ul>
     56 <li>The url of the service you want to access.</li>
     57 <li>The <strong>auth scope</strong>, which is a string that defines the specific
     58 type of access your app is asking for. For instance, the auth scope for
     59 read-only access to Google Tasks is <code>View your tasks</code>, while the auth
     60 scope for read-write access to Google Tasks is <code>Manage Your
     61 Tasks</code>.</li>
     62 <li>A <strong>client id</strong> and <strong>client secret</strong>, which are
     63 strings that identify your app to the service. You need to obtain these strings
     64 directly from the service owner. Google has a self-service system for obtaining
     65 client ids and secrets. The article <a
     66 href="http://code.google.com/apis/tasks/articles/oauth-and-tasks-on-android.html">Getting
     67 Started with the Tasks API and OAuth 2.0 on Android</a> explains
     68 how to use this system to obtain these values for use with the Google Tasks
     69 API.</li>
     70 </ul>
     71 
     72 
     73 <h2 id="RequestToken">Request an Auth Token</h2>
     74 
     75 <p>Now you're ready to request an auth token. This is a multi-step process.</p>
     76 
     77 <img src="{@docRoot}images/training/oauth_dance.png" alt="Procedure for obtaining
     78 a valid auth token from the Android Account Manager"/>
     79 
     80 <p>To get an auth token you first need to request the
     81 {@link android.Manifest.permission#ACCOUNT_MANAGER}
     82 to yourmanifest file. To actually do anything useful with the
     83 token, you'll also need to add the {@link android.Manifest.permission#INTERNET}
     84 permission.</p>
     85 
     86 <pre>
     87 &lt;manifest ... >
     88     &lt;uses-permission android:name="android.permission.ACCOUNT_MANAGER" /&gt;
     89     &lt;uses-permission android:name="android.permission.INTERNET" /&gt;
     90     ...
     91 &lt;/manifest>
     92 </pre>
     93 
     94 
     95 <p>Once your app has these permissions set, you can call {@link
     96 android.accounts.AccountManager#getAuthToken AccountManager.getAuthToken()} to get the
     97 token.</p>
     98 
     99 <p>Watch out! Calling methods on {@link android.accounts.AccountManager} can be tricky! Since
    100 account operations may involve network communication, most of the {@link
    101 android.accounts.AccountManager} methods are asynchronous. This means that instead of doing all of
    102 your auth work in one function, you need to implement it as a series of callbacks. For example:</p>
    103 
    104 <pre>
    105 AccountManager am = AccountManager.get(this);
    106 Bundle options = new Bundle();
    107 
    108 am.getAuthToken(
    109     myAccount_,                     // Account retrieved using getAccountsByType()
    110     "Manage your tasks",            // Auth scope
    111     options,                        // Authenticator-specific options
    112     this,                           // Your activity
    113     new OnTokenAcquired(),          // Callback called when a token is successfully acquired
    114     new Handler(new OnError()));    // Callback called if an error occurs
    115 </pre>
    116 
    117 <p>In this example, <code>OnTokenAcquired</code> is a class that implements
    118 {@link android.accounts.AccountManagerCallback}. {@link android.accounts.AccountManager} calls
    119 {@link android.accounts.AccountManagerCallback#run run()} on <code>OnTokenAcquired</code> with an
    120 {@link android.accounts.AccountManagerFuture} that contains a {@link android.os.Bundle}. If
    121 the call succeeded, the token is inside
    122 the {@link android.os.Bundle}.</p>
    123 
    124 <p>Here's how you can get the token from the {@link android.os.Bundle}:</p>
    125 
    126 <pre>
    127 private class OnTokenAcquired implements AccountManagerCallback&lt;Bundle&gt; {
    128     &#64;Override
    129     public void run(AccountManagerFuture&lt;Bundle&gt; result) {
    130         // Get the result of the operation from the AccountManagerFuture.
    131         Bundle bundle = result.getResult();
    132     
    133         // The token is a named value in the bundle. The name of the value
    134         // is stored in the constant AccountManager.KEY_AUTHTOKEN.
    135         token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
    136         ...
    137     }
    138 }
    139 </pre>
    140 
    141 <p>If all goes well, the {@link android.os.Bundle} contains a valid token in the {@link
    142 android.accounts.AccountManager#KEY_AUTHTOKEN} key and you're off to the races. Things don't
    143 always go that smoothly, though...</p>
    144 
    145 
    146 <h2 id="RequestAgain">Request an Auth Token... Again</h2>
    147 
    148 <p>Your first request for an auth token might fail for several reasons:</p>
    149 
    150 <ul>
    151 <li>An error in the device or network caused {@link android.accounts.AccountManager} to fail.</li>
    152 <li>The user decided not to grant your app access to the account.</li>
    153 <li>The stored account credentials aren't sufficient to gain access to the account.</li>
    154 <li>The cached auth token has expired.</li>
    155 </ul>
    156 
    157 <p>Applications can handle the first two cases trivially, usually by simply
    158 showing an error message to the user. If the network is down or the user decided
    159 not to grant access, there's not much that your application can do about it. The
    160 last two cases are a little more complicated, because well-behaved applications
    161 are expected to handle these failures automatically.</p>
    162 
    163 <p>The third failure case, having insufficient credentials, is communicated via the {@link
    164 android.os.Bundle} you receive in your {@link android.accounts.AccountManagerCallback}
    165 (<code>OnTokenAcquired</code> from the previous example). If the {@link android.os.Bundle} includes
    166 an {@link android.content.Intent} in the {@link android.accounts.AccountManager#KEY_INTENT} key,
    167 then the authenticator is telling you that it needs to interact directly with the user before it can
    168 give you a valid token.</p>
    169 
    170 <p>There may be many reasons for the authenticator to return an {@link android.content.Intent}. It
    171 may be the first time the user has logged in to this account. Perhaps the user's account has expired
    172 and they need to log in again, or perhaps their stored credentials are incorrect. Maybe the account
    173 requires two-factor authentication or it needs to activate the camera to do a retina scan. It
    174 doesn't really matter what the reason is. If you want a valid token, you're going to have to fire
    175 off the {@link android.content.Intent} to get it.</p>
    176 
    177 <pre>
    178 private class OnTokenAcquired implements AccountManagerCallback&lt;Bundle&gt; {
    179     &#64;Override
    180     public void run(AccountManagerFuture&lt;Bundle&gt; result) {
    181         ...
    182         Intent launch = (Intent) result.getResult().get(AccountManager.KEY_INTENT);
    183         if (launch != null) {
    184             startActivityForResult(launch, 0);
    185             return;
    186         }
    187     }
    188 }
    189 </pre>
    190 
    191 <p>Note that the example uses {@link android.app.Activity#startActivityForResult
    192 startActivityForResult()}, so that you can capture
    193 the result of the {@link android.content.Intent} by implementing {@link
    194 android.app.Activity#onActivityResult onActivityResult()} in
    195 your own activity. This is important! If you don't capture the result from the
    196 authenticator's response {@link android.content.Intent},
    197 it's impossible to tell whether the user has successfully authenticated or not.
    198 If the result is {@link android.app.Activity#RESULT_OK}, then the
    199 authenticator has updated the stored credentials so that they are sufficient for
    200 the level of access you requested, and you should call {@link
    201 android.accounts.AccountManager#getAuthToken AccountManager.getAuthToken()} again to request the new
    202 auth token.</p>
    203 
    204 <p>The last case, where the token has expired, it is not actually an {@link
    205 android.accounts.AccountManager} failure. The only way to discover whether a token is expired or not
    206 is to contact the server, and it would be wasteful and expensive for {@link
    207 android.accounts.AccountManager} to continually go online to check the state of all of its tokens.
    208 So this is a failure that can only be detected when an application like yours tries to use the auth
    209 token to access an online service.</p>
    210 
    211 
    212 <h2 id="ConnectToService">Connect to the Online Service</h2>
    213 
    214 <p>The example below shows how to connect to a Google server. Since Google uses the
    215 industry standard OAuth2 protocol to
    216 authenticate requests, the techniques discussed here are broadly
    217 applicable. Keep in mind, though, that every
    218 server is different. You may find yourself needing to make minor adjustments to
    219 these instructions to account for your specific
    220 situation.</p>
    221 
    222 <p>The Google APIs require you to supply four values with each request: the API
    223 key, the client ID, the client secret,
    224 and the auth key. The first three come from the Google API Console
    225 website. The last is the string value you
    226 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
    227 Google Server as part of
    228 an HTTP request.</p>
    229 
    230 <pre>
    231 URL url = new URL("https://www.googleapis.com/tasks/v1/users/&#64;me/lists?key=" + <em>your_api_key</em>);
    232 URLConnection conn = (HttpURLConnection) url.openConnection();
    233 conn.addRequestProperty("client_id", <em>your client id</em>);
    234 conn.addRequestProperty("client_secret", <em>your client secret</em>);
    235 conn.setRequestProperty("Authorization", "OAuth " + token);
    236 </pre>
    237 
    238 <p>If the request returns
    239 an HTTP error code of 401, then your token has been denied. As mentioned in the
    240 last section, the most common reason for
    241 this is that the token has expired. The fix is
    242 simple: call
    243 {@link android.accounts.AccountManager#invalidateAuthToken AccountManager.invalidateAuthToken()} and
    244 repeat the token acquisition dance one
    245 more time.</p>
    246 
    247 <p>Because expired tokens are such a common occurrence, and fixing them  is so easy, many
    248 applications just assume the token has expired before even asking for it. If renewing a token is a
    249 cheap operation for your server, you might prefer to call {@link
    250 android.accounts.AccountManager#invalidateAuthToken AccountManager.invalidateAuthToken()} before the
    251 first call to {@link android.accounts.AccountManager#getAuthToken AccountManager.getAuthToken()},
    252 and spare yourself the need to request an auth token twice.</p>
    253