Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f9cca66e authored by Scott Main's avatar Scott Main
Browse files

Docs: Training class on Identifying and Authenticating Users.

Change-Id: Ie2005687ca3baf28d9e01be8c10ee0f6a58a3cc3
parent 56a3e326
Loading
Loading
Loading
Loading
+253 −0
Original line number Diff line number Diff line
page.title=Authenticating to OAuth2 Services
parent.title=Identifying and Authenticating Users
parent.link=index.html

trainingnavtop=true
previous.title=Identifying 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&mdash;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. Auth tokens usually expire after
some period of time, so you'll have to renew them.</p>

 <!-- TODO: I think a flowchart would be useful here, or perhaps a link to an as-yet-to-be-created
flowchart that lives in the docs. -->

<p>To get an auth token you first need to request the
{@link android.Manifest.permission#ACCOUNT_MANAGER}
to yourmanifest file. To actually do anything useful with the
token, you'll also need to add the {@link android.Manifest.permission#INTERNET}
permission.</p>

<code>
&lt;manifest ... >
    &lt;uses-permission android:name="android.permission.ACCOUNT_MANAGER" /&gt;
    &lt;uses-permission android:name="android.permission.INTERNET" /&gt;
    ...
&lt;/manifest>
</code>


<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 extends
{@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&lt;Bundle&gt; {
    &#64;Override
    public void run(AccountManagerFuture&lt;Bundle&gt; 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&lt;Bundle&gt; {
    &#64;Override
    public void run(AccountManagerFuture&lt;Bundle&gt; result) {
        ...
        Intent launch = (Intent) result.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/&#64;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>
+185 −0
Original line number Diff line number Diff line
page.title=Creating a Custom Account Type
parent.title=Identifying and Authenticating Users
parent.link=index.html

trainingnavtop=true
previous.title=Authenticating to OAuth2 Services
previous.link=authenticate.html

@jd:body

<div id="tb-wrapper">
  <div id="tb">
<h2>This lesson teaches you to</h2>
<ol>
  <li><a href="#AccountCode">Implement Your Custom Account Code</a></li>
  <li><a href="#Security">Be Smart About Security!</a></li>
  <li><a href="#ExtendThatThing">Extend AbstractAccountAuthenticator</a></li>
  <li><a href="#TaskFour">Create an Authenticator Service</a></li>
  <li><a href="#DistributeService">Distribute Your Service</a></li>
</ol>

<h2>You should also read</h2>
<ul>
  <li><a
href="http://developer.android.com/resources/samples/SampleSyncAdapter/index.html">
SampleSyncAdapter app</a></li>
</ul>
  </div>
</div>

<p>In the previous lessons, we've talked about using Google accounts to identify Google users and
access Google APIs. But what if you've got your own online service? It turns out
to be relatively straightforward to install new account types on a user's
device. This lesson explains how to create a custom account type that works the
same way as the built-in accounts do. </p>


<h2 id="AccountCode">Implement Your Custom Account Code</h2>

<p>The first thing you'll need is a way to get credentials from the user. This
may be as simple as a dialog box that asks for a name and a password. Or it may
be a more exotic procedure like a one-time password or a biometric scan. Either
way, it's your responsibility to implement the code that:</p>
<ol>
  <li>Collects credentials from the user</li>
  <li>Authenticates the credentials with the server</li>
  <li>Stores the credentials on the device</li>
</ol>


<p>Typically all three of these requirements can be handled by one activity. We'll call this the
authenticator activity.</p>

<p>Because they need to interact with the {@link android.accounts.AccountManager} system,
authenticator activities have certain requirements that normal activities don't. To make it easy to
get things right, the Android framework supplies a base class, {@link
android.accounts.AccountAuthenticatorActivity}, which you can extend to create your own custom
authenticator.</p>

<p>How you address the first two requirements of an authenticator activity,
credential collection and authentication, is completely up to you. (If there
were only one way to do it, there'd be no need for "custom" account types, after
all.) The third requirement has a canonical, and rather simple,
implementation:</p>

<pre>
final Account account = new Account(mUsername, <em>your_account_type</em>);
mAccountManager.addAccountExplicitly(account, mPassword, null);
</pre>


<h2 id="Security">Be Smart About Security!</h2>

<p>It's important to understand that {@link android.accounts.AccountManager} is not an encryption
service
or a keychain. It stores account credentials just as you pass them, in <strong>plain
text</strong>. On most devices, this isn't
a particular concern, because it stores them in
a database that is only accessible to root. But on a rooted device, the
credentials would be readable by anyone with {@code adb} access to the device.</p>

<p>With this in mind, you shouldn't pass the user's actual
password to {@link android.accounts.AccountManager#addAccountExplicitly 
AccountManager.addAccountExplicitly()}. Instead, you should store a
cryptographically secure token that would be of limited use to an attacker. If your
user credentials are protecting something valuable, you should carefully
consider doing something similar.</p>

<p class="caution"><strong>Remember:</strong> When it comes to security code, follow the
"Mythbusters" rule: don't try this at home! Consult a security professional before implementing any
custom account code.</p>

<p>Now that the security disclaimers are out of the way, it's time to get back to work.
You've already implemented the meat of your custom account code; what's left is
plumbing.</p>


<h2 id="ExtendThatThing">Extend AbstractAccountAuthenticator</h2>

<p>In order for the {@link android.accounts.AccountManager} to work with your custom account
code, you
need a class that implements the interfaces that {@link android.accounts.AccountManager} expects.
This class is the <em>authenticator class</em>.</p>

<p>The easiest way to create an authenticator class is to extend
{@link android.accounts.AbstractAccountAuthenticator} and implement its abstract methods. If you've
worked through the previous lessons, the abstract methods of
{@link android.accounts.AbstractAccountAuthenticator} should look familiar: they're the opposite
side of
the methods you called in the previous lesson to get account information and
authorization tokens.</p>

<p>Implementing an authenticator class properly requires a number of separate
pieces of code. First, {@link android.accounts.AbstractAccountAuthenticator} has seven abstract
methods that you must override. Second, you need to add an
<a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">intent filter</a> for
<code>"android.accounts.AccountAuthenticator"</code> to your application
manifest (shown in the next section). Finally, you must supply two XML resources that define, among
other
things, the name of your custom account type and the icon that the system will
display next to accounts of this type.</p>

<p> You can find a step-by-step guide to implementing a successful authenticator class and the XML
files in the {@link android.accounts.AbstractAccountAuthenticator} documentation. There's also a
sample implementation in the <a
href="http://developer.android.com/resources/samples/SampleSyncAdapter/index.html">
SampleSyncAdapter sample app</a>.</p>

<p>As you read through the SampleSyncAdapter code, you'll notice that several of
the methods return an intent in a bundle. This is the same intent that will be
used to launch your custom authenticator activity. If your authenticator
activity needs any special initialization parameters, you can attach them to the
intent using {@link android.content.Intent#putExtra Intent.putExtra()}.</p>


<h2 id="TaskFour">Create an Authenticator Service</h2>

<p>Now that you have an authenticator class, you need a place for it to live.
Account authenticators need to be available to multiple applications and work in
the background, so naturally they're required to run inside a {@link android.app.Service}. We'll
call this the authenticator service.</p>

<p>Your authenticator service can be very simple. All it needs to do is create
an instance of your authenticator class in {@link android.app.Service#onCreate onCreate()} and call
{@link android.accounts.AbstractAccountAuthenticator#getIBinder getIBinder()} in {@link
android.app.Service#onBind onBind()}. The <a
href="http://developer.android.com/resources/samples/SampleSyncAdapter/index.html">
SampleSyncAdapter</a> contains a good example of an authenticator service.</p>

<p>Don't forget to add a {@code &lt;service&gt;} tag to your manifest file
and add an intent filter for the AccountAuthenticator intent and declare the account
authenticator:</p>

<pre>
&lt;service ...>
   &lt;intent-filter>
      &lt;action android:name="android.accounts.AccountAuthenticator" />
   &lt;/intent-filter>
   &lt;meta-data android:name="android.accounts.AccountAuthenticator"
             android:resource="@xml/authenticator" />
&lt;/service>
</pre>


<h2 id="DistributeService">Distribute Your Service</h2>

<p>You're done! The system now recognizes your account type, right alongside all
the big name account types like "Google" and "Corporate." You can use the
<strong>Accounts &amp; Sync</strong> Settings page to add an account, and apps that ask for
accounts of your custom type will be able to enumerate and authenticate just as
they would with any other account type.</p>

<p>Of course, all of this assumes that your account service is actually
installed on the device. If only one app will ever access the service, then
this isn't a big deal&mdash;just bundle the service in the app.
But if you want your account service to be used by more than one app, things get
trickier. You don't want to bundle the service with all of your apps and have
multiple copies of it taking up space on your user's device.</p>

<p>One solution is to place the service in one small, special-purpose APK. When
an app wishes to use your custom account type, it can check the device to see if
your custom account service is available. If not, it can direct the user to
Android Market to download the service. This may seem like a great deal of
trouble at first, but compared with the alternative of re-entering credentials
for every app that uses your custom account, it's refreshingly easy.</p>
+137 −0

File added.

Preview size limit exceeded, changes collapsed.

+65 −0
Original line number Diff line number Diff line
page.title=Identifying and Authenticating Users

trainingnavtop=true
startpage=true
next.title=Identifying Your User
next.link=identify.html

@jd:body


<div id="tb-wrapper">
<div id="tb">

<h2>Requirements and prerequisites</h2>
<ul>
  <li>Android 2.0 (API level 5) or higher</li>
  <li>Experience with <a href="{@docRoot}guide/topics/fundamentals/services.html">Services</a></li>
  <li>Experience with <a href="http://oauth.net/2/">OAuth 2.0</a></li>
</ul>  

<h2>You should also read</h2>
<ul>
  <li><a
href="http://developer.android.com/resources/samples/SampleSyncAdapter/index.html">
SampleSyncAdapter app</a></li>
</ul>

</div>
</div>


<p>Android users get attached to their devices and to applications that they
love. One way to make your application lovable is to make it personal. Android
devices know who your user is, what services they have access to, and where they
store your data. With your user's permission, you can use that information to
make your application a richer, more personal experience.</p>

<p>In this class, you will learn multiple techniques for interacting with your
user's identity, enabling you to:</p>

<ul>
<li>Identify the user by detecting and selecting an account
<li>Authenticate the user to make sure they are who they say they are
<li>Gain permission to access the user's online data via services like
the Google APIs
<li>Add a custom account to the user's device to authenticate your own
back-end services
</ul>


<h2>Lessons</h2>

<dl>
  <dt><b><a href="identify.html">Identifying Your User</a></b></dt>
    <dd>Use {@link android.accounts.AccountManager} to learn the user's account name(s).</dd>

  <dt><b><a href="authenticate.html">Authenticating to OAuth2 Services</a></b></dt>
    <dd> Use OAuth2 to help users get permission to access web services without needing to type in a
login name or password. </dd>

  <dt><b><a href="custom_auth.html">Creating a Custom Account Type</a></b></dt>
    <dd>Add your own account type to the Android Account Manager.</dd>

</dl>