From f9cca66e8b4acbc8d50713b4ed4b92d8274285c1 Mon Sep 17 00:00:00 2001 From: Scott Main Date: Thu, 15 Dec 2011 10:07:03 -0800 Subject: Docs: Training class on Identifying and Authenticating Users. Change-Id: Ie2005687ca3baf28d9e01be8c10ee0f6a58a3cc3 --- docs/html/training/id-auth/authenticate.jd | 253 +++++++++++++++++++++++++++++ docs/html/training/id-auth/custom_auth.jd | 185 +++++++++++++++++++++ docs/html/training/id-auth/identify.jd | 137 ++++++++++++++++ docs/html/training/id-auth/index.jd | 65 ++++++++ 4 files changed, 640 insertions(+) create mode 100644 docs/html/training/id-auth/authenticate.jd create mode 100644 docs/html/training/id-auth/custom_auth.jd create mode 100644 docs/html/training/id-auth/identify.jd create mode 100644 docs/html/training/id-auth/index.jd (limited to 'docs/html/training/id-auth') diff --git a/docs/html/training/id-auth/authenticate.jd b/docs/html/training/id-auth/authenticate.jd new file mode 100644 index 0000000..31352e7 --- /dev/null +++ b/docs/html/training/id-auth/authenticate.jd @@ -0,0 +1,253 @@ +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 + + +
+ +
+ +

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.

+ +

The industry standard way to deal with authentication to third-party services +is the OAuth2 protocol. OAuth2 provides a single value, called an auth +token, 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.

+ +

Using OAuth2 is good for:

+ + + +

Gather Information

+ +

To begin using OAuth2, you need to know a few things about the API you're trying +to access:

+ + + + +

Request an Auth Token

+ +

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.

+ + + +

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.

+ + +<manifest ... > + <uses-permission android:name="android.permission.ACCOUNT_MANAGER" /> + <uses-permission android:name="android.permission.INTERNET" /> + ... +</manifest> + + + +

Once your app has these permissions set, you can call {@link +android.accounts.AccountManager#getAuthToken AccountManager.getAuthToken()} to get the +token.

+ +

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:

+ +
+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
+
+ +

In this example, OnTokenAcquired is a class that extends +{@link android.accounts.AccountManagerCallback}. {@link android.accounts.AccountManager} calls +{@link android.accounts.AccountManagerCallback#run run()} on OnTokenAcquired 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}.

+ +

Here's how you can get the token from the {@link android.os.Bundle}:

+ +
+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);
+        ...
+    }
+}
+
+ +

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...

+ + +

Request an Auth Token... Again

+ +

Your first request for an auth token might fail for several reasons:

+ + + +

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.

+ +

The third failure case, having insufficient credentials, is communicated via the {@link +android.os.Bundle} you receive in your {@link android.accounts.AccountManagerCallback} +(OnTokenAcquired 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.

+ +

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.

+ +
+private class OnTokenAcquired implements AccountManagerCallback<Bundle> {
+    @Override
+    public void run(AccountManagerFuture<Bundle> result) {
+        ...
+        Intent launch = (Intent) result.get(AccountManager.KEY_INTENT);
+        if (launch != null) {
+            startActivityForResult(launch, 0);
+            return;
+        }
+    }
+}
+
+ +

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.

+ +

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.

+ + +

Connect to the Online Service

+ +

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.

+ +

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.

+ +
+URL url = new URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=" + your_api_key);
+URLConnection conn = (HttpURLConnection) url.openConnection();
+conn.addRequestProperty("client_id", your client id);
+conn.addRequestProperty("client_secret", your client secret);
+conn.setRequestProperty("Authorization", "OAuth " + token);
+
+ +

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.

+ +

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.

diff --git a/docs/html/training/id-auth/custom_auth.jd b/docs/html/training/id-auth/custom_auth.jd new file mode 100644 index 0000000..4f59746 --- /dev/null +++ b/docs/html/training/id-auth/custom_auth.jd @@ -0,0 +1,185 @@ +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 + +
+ +
+ +

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.

+ + +

Implement Your Custom Account Code

+ +

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:

+
    +
  1. Collects credentials from the user
  2. +
  3. Authenticates the credentials with the server
  4. +
  5. Stores the credentials on the device
  6. +
+ + +

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

+ +

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.

+ +

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:

+ +
+final Account account = new Account(mUsername, your_account_type);
+mAccountManager.addAccountExplicitly(account, mPassword, null);
+
+ + +

Be Smart About Security!

+ +

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 plain +text. 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.

+ +

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.

+ +

Remember: 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.

+ +

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.

+ + +

Extend AbstractAccountAuthenticator

+ +

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 authenticator class.

+ +

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.

+ +

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 +intent filter for +"android.accounts.AccountAuthenticator" 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.

+ +

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 +SampleSyncAdapter sample app.

+ +

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()}.

+ + +

Create an Authenticator Service

+ +

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.

+ +

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 +SampleSyncAdapter contains a good example of an authenticator service.

+ +

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

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

Distribute Your Service

+ +

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 +Accounts & Sync 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.

+ +

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—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.

+ +

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.

diff --git a/docs/html/training/id-auth/identify.jd b/docs/html/training/id-auth/identify.jd new file mode 100644 index 0000000..f18a34f --- /dev/null +++ b/docs/html/training/id-auth/identify.jd @@ -0,0 +1,137 @@ +page.title=Identifying Your User +parent.title=Identifying and Authenticating Users +parent.link=index.html + +trainingnavtop=true +next.title=Authenticating to OAuth2 Services +next.link=authenticate.html + +@jd:body + + +
+ +
+ + +

Everyone likes it when you remember their name. One of the simplest, most +effective things you can do to make your app more lovable is to remember who +your user is—especially when the user upgrades to a new device or starts carrying +a tablet as well as a phone. But how do you know who your user is? And how do +you recognize them on a new device?

+ +

For many applications, the answer is the {@link android.accounts.AccountManager} APIs. With the +user's permission, you can use Account Manager to uniquely identify a user +by the online identity that the user has stored on their device.

+ +

Integration with the user's accounts allows you to do a variety of things such as:

+ + + +

Determine if AccountManager for You

+ +

Applications typically identify the user in three different ways:

+
    +
  1. Ask the user to type in a username
  2. +
  3. Use a unique device identifier rather than a user identifier
  4. +
  5. Retrieve a built-in account from {@link android.accounts.AccountManager}
  6. +
+ +

Option (a) is problematic. First, asking the user to type something before +entering your app will automatically make your app less appealing. Second, +there's no guarantee that the username chosen will be unique.

+ +

Option (b) is less onerous for the user, but it's +tricky +to get right. More +importantly, it only allows you to remember the user on one device. Imagine the +frustration of someone who upgrades to a shiny new device, only to find that +your app no longer remembers them.

+ +

Option (c) is the preferred technique. Account Manager allows you to get +information about the accounts that are stored on the user's device. As we'll +see in this lesson, using Account Manager lets you identify your user, no matter +how many devices the user may own, by adding just a couple of extra taps to your +UI.

+ + +

Decide What Type of Account to Use

+ +

Android devices can store multiple accounts from many different providers. +When you query {@link android.accounts.AccountManager} for account names, you can choose to filter +by +account type. The account type is a string that uniquely identifies the entity +that issued the account. For instance, Google accounts have type "com.google," +while Twitter uses "com.twitter.android.auth.login."

+ + +

Request GET_ACCOUNT permission

+ +

In order to get a list of accounts on the device, your app needs the {@link +android.Manifest.permission#GET_ACCOUNTS} +permission. Add a {@code +<uses-permission>} tag in your manifest file to request +this permission:

+ +
+<manifest ... >
+    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+    ...
+</manifest>
+
+ + +

Query AccountManager for a List of Accounts

+ +

Once you decide what account type you're interested in, you need to query for accounts of that +type. Get an instance of {@link android.accounts.AccountManager} by calling {@link +android.accounts.AccountManager#get(android.content.Context) AccountManager.get()}. Then use that +instance to call {@link android.accounts.AccountManager#getAccountsByType(java.lang.String) +getAccountsByType()}.

+ +
+AccountManager am = AccountManager.get(this); // "this" references the current Context
+
+Account[] accounts = am.getAccountsByType("com.google");
+
+ +

This returns an array of {@link android.accounts.Account} objects. If there's more than one +{@link android.accounts.Account} in +the array, you should present a dialog asking the user to select one.

+ + +

Use the Account Object to Identify the User

+ +

The {@link android.accounts.Account} object contains an account name, which for Google accounts +is an +email address. You can use this information in several different ways, such as: +

+

+ + +

Decide Whether Identification is Enough

+ +

Account names are a good way to identify the user, but the {@link android.accounts.Account} +object by +itself doesn't protect your data or give you access to anything. If you intend +to access private data, you'll need something stronger: authentication. +The next lesson explains how to authenticate to existing online services. The lesson after that +deals with writing a custom authenticator so that you can install your own +account types.

diff --git a/docs/html/training/id-auth/index.jd b/docs/html/training/id-auth/index.jd new file mode 100644 index 0000000..6fbfa65 --- /dev/null +++ b/docs/html/training/id-auth/index.jd @@ -0,0 +1,65 @@ +page.title=Identifying and Authenticating Users + +trainingnavtop=true +startpage=true +next.title=Identifying Your User +next.link=identify.html + +@jd:body + + +
+
+ +

Requirements and prerequisites

+
    +
  • Android 2.0 (API level 5) or higher
  • +
  • Experience with Services
  • +
  • Experience with OAuth 2.0
  • +
+ +

You should also read

+ + +
+
+ + +

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.

+ +

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

+ + + + +

Lessons

+ +
+
Identifying Your User
+
Use {@link android.accounts.AccountManager} to learn the user's account name(s).
+ +
Authenticating to OAuth2 Services
+
Use OAuth2 to help users get permission to access web services without needing to type in a +login name or password.
+ +
Creating a Custom Account Type
+
Add your own account type to the Android Account Manager.
+ +
+ -- cgit v1.1