authenticate.jd revision 98af5edc70b0b418366f5197b9aeef30f135d40e
1page.title=Authenticating to OAuth2 Services
2parent.title=Remembering and Authenticating Users
3parent.link=index.html
4
5trainingnavtop=true
6previous.title=Remembering Your User
7previous.link=identify.html
8next.title=Creating a Custom Account Type
9next.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
27the service&mdash;they need to provide proof of their identity. For an
28application that accesses a third-party service, the security problem is even
29more complicated. Not only does the user need to be authenticated to access the
30service, but the application also needs to be authorized to act on the user's
31behalf. </p>
32
33<p>The industry standard way to deal with authentication to third-party services
34is the OAuth2 protocol. OAuth2 provides a single value, called an <strong>auth
35token</strong>, that represents both the user's identity and the application's
36authorization to act on the user's behalf. This lesson demonstrates connecting
37to a Google server that supports OAuth2. Although Google services are used as an
38example, the techniques demonstrated will work on any service that correctly
39supports 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
44her 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
53to 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
58type of access your app is asking for. For instance, the auth scope for
59read-only access to Google Tasks is <code>View your tasks</code>, while the auth
60scope for read-write access to Google Tasks is <code>Manage Your
61Tasks</code>.</li>
62<li>A <strong>client id</strong> and <strong>client secret</strong>, which are
63strings that identify your app to the service. You need to obtain these strings
64directly from the service owner. Google has a self-service system for obtaining
65client ids and secrets. The article <a
66href="http://code.google.com/apis/tasks/articles/oauth-and-tasks-on-android.
67html">Getting Started with the Tasks API and OAuth 2.0 on Android</a> explains
68how to use this system to obtain these values for use with the Google Tasks
69API.</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
78a 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}
82to yourmanifest file. To actually do anything useful with the
83token, you'll also need to add the {@link android.Manifest.permission#INTERNET}
84permission.</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
96android.accounts.AccountManager#getAuthToken AccountManager.getAuthToken()} to get the
97token.</p>
98
99<p>Watch out! Calling methods on {@link android.accounts.AccountManager} can be tricky! Since
100account operations may involve network communication, most of the {@link
101android.accounts.AccountManager} methods are asynchronous. This means that instead of doing all of
102your auth work in one function, you need to implement it as a series of callbacks. For example:</p>
103
104<pre>
105AccountManager am = AccountManager.get(this);
106Bundle options = new Bundle();
107
108am.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 extends
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
121the call succeeded, the token is inside
122the {@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>
127private 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
142android.accounts.AccountManager#KEY_AUTHTOKEN} key and you're off to the races. Things don't
143always 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
158showing an error message to the user. If the network is down or the user decided
159not to grant access, there's not much that your application can do about it. The
160last two cases are a little more complicated, because well-behaved applications
161are expected to handle these failures automatically.</p>
162
163<p>The third failure case, having insufficient credentials, is communicated via the {@link
164android.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
166an {@link android.content.Intent} in the {@link android.accounts.AccountManager#KEY_INTENT} key,
167then the authenticator is telling you that it needs to interact directly with the user before it can
168give you a valid token.</p>
169
170<p>There may be many reasons for the authenticator to return an {@link android.content.Intent}. It
171may be the first time the user has logged in to this account. Perhaps the user's account has expired
172and they need to log in again, or perhaps their stored credentials are incorrect. Maybe the account
173requires two-factor authentication or it needs to activate the camera to do a retina scan. It
174doesn't really matter what the reason is. If you want a valid token, you're going to have to fire
175off the {@link android.content.Intent} to get it.</p>
176
177<pre>
178private 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.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
192startActivityForResult()}, so that you can capture
193the result of the {@link android.content.Intent} by implementing {@link
194android.app.Activity#onActivityResult onActivityResult()} in
195your own activity. This is important! If you don't capture the result from the
196authenticator's response {@link android.content.Intent},
197it's impossible to tell whether the user has successfully authenticated or not.
198If the result is {@link android.app.Activity#RESULT_OK}, then the
199authenticator has updated the stored credentials so that they are sufficient for
200the level of access you requested, and you should call {@link
201android.accounts.AccountManager#getAuthToken AccountManager.getAuthToken()} again to request the new
202auth token.</p>
203
204<p>The last case, where the token has expired, it is not actually an {@link
205android.accounts.AccountManager} failure. The only way to discover whether a token is expired or not
206is to contact the server, and it would be wasteful and expensive for {@link
207android.accounts.AccountManager} to continually go online to check the state of all of its tokens.
208So this is a failure that can only be detected when an application like yours tries to use the auth
209token 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
215industry standard OAuth2 protocol to
216authenticate requests, the techniques discussed here are broadly
217applicable. Keep in mind, though, that every
218server is different. You may find yourself needing to make minor adjustments to
219these instructions to account for your specific
220situation.</p>
221
222<p>The Google APIs require you to supply four values with each request: the API
223key, the client ID, the client secret,
224and the auth key. The first three come from the Google API Console
225website. The last is the string value you
226obtained 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
227Google Server as part of
228an HTTP request.</p>
229
230<pre>
231URL url = new URL("https://www.googleapis.com/tasks/v1/users/&#64;me/lists?key=" + <em>your_api_key</em>);
232URLConnection conn = (HttpURLConnection) url.openConnection();
233conn.addRequestProperty("client_id", <em>your client id</em>);
234conn.addRequestProperty("client_secret", <em>your client secret</em>);
235conn.setRequestProperty("Authorization", "OAuth " + token);
236</pre>
237
238<p>If the request returns
239an HTTP error code of 401, then your token has been denied. As mentioned in the
240last section, the most common reason for
241this is that the token has expired. The fix is
242simple: call
243{@link android.accounts.AccountManager#invalidateAuthToken AccountManager.invalidateAuthToken()} and
244repeat the token acquisition dance one
245more time.</p>
246
247<p>Because expired tokens are such a common occurrence, and fixing them  is so easy, many
248applications just assume the token has expired before even asking for it. If renewing a token is a
249cheap operation for your server, you might prefer to call {@link
250android.accounts.AccountManager#invalidateAuthToken AccountManager.invalidateAuthToken()} before the
251first call to {@link android.accounts.AccountManager#getAuthToken AccountManager.getAuthToken()},
252and spare yourself the need to request an auth token twice.</p>
253