Skip to content

Commit f9cca66

Browse files
committed
Docs: Training class on Identifying and Authenticating Users.
Change-Id: Ie2005687ca3baf28d9e01be8c10ee0f6a58a3cc3
1 parent 56a3e32 commit f9cca66

File tree

4 files changed

+640
-0
lines changed

4 files changed

+640
-0
lines changed
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
page.title=Authenticating to OAuth2 Services
2+
parent.title=Identifying and Authenticating Users
3+
parent.link=index.html
4+
5+
trainingnavtop=true
6+
previous.title=Identifying 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.
67+
html">Getting 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. Auth tokens usually expire after
76+
some period of time, so you'll have to renew them.</p>
77+
78+
<!-- TODO: I think a flowchart would be useful here, or perhaps a link to an as-yet-to-be-created
79+
flowchart that lives in the docs. -->
80+
81+
<p>To get an auth token you first need to request the
82+
{@link android.Manifest.permission#ACCOUNT_MANAGER}
83+
to yourmanifest file. To actually do anything useful with the
84+
token, you'll also need to add the {@link android.Manifest.permission#INTERNET}
85+
permission.</p>
86+
87+
<code>
88+
&lt;manifest ... >
89+
&lt;uses-permission android:name="android.permission.ACCOUNT_MANAGER" /&gt;
90+
&lt;uses-permission android:name="android.permission.INTERNET" /&gt;
91+
...
92+
&lt;/manifest>
93+
</code>
94+
95+
96+
<p>Once your app has these permissions set, you can call {@link
97+
android.accounts.AccountManager#getAuthToken AccountManager.getAuthToken()} to get the
98+
token.</p>
99+
100+
<p>Watch out! Calling methods on {@link android.accounts.AccountManager} can be tricky! Since
101+
account operations may involve network communication, most of the {@link
102+
android.accounts.AccountManager} methods are asynchronous. This means that instead of doing all of
103+
your auth work in one function, you need to implement it as a series of callbacks. For example:</p>
104+
105+
<pre>
106+
AccountManager am = AccountManager.get(this);
107+
Bundle options = new Bundle();
108+
109+
am.getAuthToken(
110+
myAccount_, // Account retrieved using getAccountsByType()
111+
"Manage your tasks", // Auth scope
112+
options, // Authenticator-specific options
113+
this, // Your activity
114+
new OnTokenAcquired(), // Callback called when a token is successfully acquired
115+
new Handler(new OnError())); // Callback called if an error occurs
116+
</pre>
117+
118+
<p>In this example, <code>OnTokenAcquired</code> is a class that extends
119+
{@link android.accounts.AccountManagerCallback}. {@link android.accounts.AccountManager} calls
120+
{@link android.accounts.AccountManagerCallback#run run()} on <code>OnTokenAcquired</code> with an
121+
{@link android.accounts.AccountManagerFuture} that contains a {@link android.os.Bundle}. If
122+
the call succeeded, the token is inside
123+
the {@link android.os.Bundle}.</p>
124+
125+
<p>Here's how you can get the token from the {@link android.os.Bundle}:</p>
126+
127+
<pre>
128+
private class OnTokenAcquired implements AccountManagerCallback&lt;Bundle&gt; {
129+
&#64;Override
130+
public void run(AccountManagerFuture&lt;Bundle&gt; result) {
131+
// Get the result of the operation from the AccountManagerFuture.
132+
Bundle bundle = result.getResult();
133+
134+
// The token is a named value in the bundle. The name of the value
135+
// is stored in the constant AccountManager.KEY_AUTHTOKEN.
136+
token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
137+
...
138+
}
139+
}
140+
</pre>
141+
142+
<p>If all goes well, the {@link android.os.Bundle} contains a valid token in the {@link
143+
android.accounts.AccountManager#KEY_AUTHTOKEN} key and you're off to the races. Things don't
144+
always go that smoothly, though...</p>
145+
146+
147+
<h2 id="RequestAgain">Request an Auth Token... Again</h2>
148+
149+
<p>Your first request for an auth token might fail for several reasons:</p>
150+
151+
<ul>
152+
<li>An error in the device or network caused {@link android.accounts.AccountManager} to fail.</li>
153+
<li>The user decided not to grant your app access to the account.</li>
154+
<li>The stored account credentials aren't sufficient to gain access to the account.</li>
155+
<li>The cached auth token has expired.</li>
156+
</ul>
157+
158+
<p>Applications can handle the first two cases trivially, usually by simply
159+
showing an error message to the user. If the network is down or the user decided
160+
not to grant access, there's not much that your application can do about it. The
161+
last two cases are a little more complicated, because well-behaved applications
162+
are expected to handle these failures automatically.</p>
163+
164+
<p>The third failure case, having insufficient credentials, is communicated via the {@link
165+
android.os.Bundle} you receive in your {@link android.accounts.AccountManagerCallback}
166+
(<code>OnTokenAcquired</code> from the previous example). If the {@link android.os.Bundle} includes
167+
an {@link android.content.Intent} in the {@link android.accounts.AccountManager#KEY_INTENT} key,
168+
then the authenticator is telling you that it needs to interact directly with the user before it can
169+
give you a valid token.</p>
170+
171+
<p>There may be many reasons for the authenticator to return an {@link android.content.Intent}. It
172+
may be the first time the user has logged in to this account. Perhaps the user's account has expired
173+
and they need to log in again, or perhaps their stored credentials are incorrect. Maybe the account
174+
requires two-factor authentication or it needs to activate the camera to do a retina scan. It
175+
doesn't really matter what the reason is. If you want a valid token, you're going to have to fire
176+
off the {@link android.content.Intent} to get it.</p>
177+
178+
<pre>
179+
private class OnTokenAcquired implements AccountManagerCallback&lt;Bundle&gt; {
180+
&#64;Override
181+
public void run(AccountManagerFuture&lt;Bundle&gt; result) {
182+
...
183+
Intent launch = (Intent) result.get(AccountManager.KEY_INTENT);
184+
if (launch != null) {
185+
startActivityForResult(launch, 0);
186+
return;
187+
}
188+
}
189+
}
190+
</pre>
191+
192+
<p>Note that the example uses {@link android.app.Activity#startActivityForResult
193+
startActivityForResult()}, so that you can capture
194+
the result of the {@link android.content.Intent} by implementing {@link
195+
android.app.Activity#onActivityResult onActivityResult()} in
196+
your own activity. This is important! If you don't capture the result from the
197+
authenticator's response {@link android.content.Intent},
198+
it's impossible to tell whether the user has successfully authenticated or not.
199+
If the result is {@link android.app.Activity#RESULT_OK}, then the
200+
authenticator has updated the stored credentials so that they are sufficient for
201+
the level of access you requested, and you should call {@link
202+
android.accounts.AccountManager#getAuthToken AccountManager.getAuthToken()} again to request the new
203+
auth token.</p>
204+
205+
<p>The last case, where the token has expired, it is not actually an {@link
206+
android.accounts.AccountManager} failure. The only way to discover whether a token is expired or not
207+
is to contact the server, and it would be wasteful and expensive for {@link
208+
android.accounts.AccountManager} to continually go online to check the state of all of its tokens.
209+
So this is a failure that can only be detected when an application like yours tries to use the auth
210+
token to access an online service.</p>
211+
212+
213+
<h2 id="ConnectToService">Connect to the Online Service</h2>
214+
215+
<p>The example below shows how to connect to a Google server. Since Google uses the
216+
industry standard OAuth2 protocol to
217+
authenticate requests, the techniques discussed here are broadly
218+
applicable. Keep in mind, though, that every
219+
server is different. You may find yourself needing to make minor adjustments to
220+
these instructions to account for your specific
221+
situation.</p>
222+
223+
<p>The Google APIs require you to supply four values with each request: the API
224+
key, the client ID, the client secret,
225+
and the auth key. The first three come from the Google API Console
226+
website. The last is the string value you
227+
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
228+
Google Server as part of
229+
an HTTP request.</p>
230+
231+
<pre>
232+
URL url = new URL("https://www.googleapis.com/tasks/v1/users/&#64;me/lists?key=" + <em>your_api_key</em>);
233+
URLConnection conn = (HttpURLConnection) url.openConnection();
234+
conn.addRequestProperty("client_id", <em>your client id</em>);
235+
conn.addRequestProperty("client_secret", <em>your client secret</em>);
236+
conn.setRequestProperty("Authorization", "OAuth " + token);
237+
</pre>
238+
239+
<p>If the request returns
240+
an HTTP error code of 401, then your token has been denied. As mentioned in the
241+
last section, the most common reason for
242+
this is that the token has expired. The fix is
243+
simple: call
244+
{@link android.accounts.AccountManager#invalidateAuthToken AccountManager.invalidateAuthToken()} and
245+
repeat the token acquisition dance one
246+
more time.</p>
247+
248+
<p>Because expired tokens are such a common occurrence, and fixing them is so easy, many
249+
applications just assume the token has expired before even asking for it. If renewing a token is a
250+
cheap operation for your server, you might prefer to call {@link
251+
android.accounts.AccountManager#invalidateAuthToken AccountManager.invalidateAuthToken()} before the
252+
first call to {@link android.accounts.AccountManager#getAuthToken AccountManager.getAuthToken()},
253+
and spare yourself the need to request an auth token twice.</p>

0 commit comments

Comments
 (0)