Skip to content

Android Authentication 0.6.x

Preethum edited this page Nov 22, 2015 · 2 revisions

Android Authentication for GSP

Authentication on Android is slightly easier than a typical Java application since Android has it's own authentication management system. We provide an implementation of the ServiceAuthenticator to be used on Android which will work against the enhanced GWT Servlets hosted on AppEngine (See OAuth-2.0-App-Engine-Authentication). The classes provided and described below are utilizing interfaces designed for flexible use as needed, but we will highlight examples that make its usage much easier. We provide two currently functional authenticators and a number of assistance classes.

Authenticator Setup

Both authenticators have some common points. Both require GoogleOAuthIdManagers and ServiceAuthenticationListeners. Both use a builder paradigm to create it. Both will also need to be prepared, and each will describe points relevant to the preparation. An understanding is beneficial to the usage, but not required. Preparation is simply a method call: authenticator.prepareAuthentication() will ultimately will result in a call to your ServiceAuthenticationListener once ready.

OAuth ID Manager and Listener

First, you must provide an implementation of GoogleOAuthIdManager that the authenticator will need. Typically, this should be a secured, but for example purposes, the SPAAppTest uses the following GoogleOAuthIdManager to provide this information to authenticator:

GoogleOAuthIdManager manager = new GoogleOAuthIdManager() {
	@Override
	public String getServerClientId(Context context) {
		return context.getString(R.string.gae_client_id);
	}
};

The ServiceAuthenticationListener simply provides an asynchronous control mechanism to act out once an authenticator is available. Implement an instance (either anonymous inner or declared) and pass it into the builder constructor.

Cross-Client Authenticator

The AndroidGAECrossClientAuthenticator class provides a straight-forward way to utilize Google accounts on Android within an Activity (or Fragment). Since it utilizes the accounts system on Google, there is some management that must be handled in terms of ActivityResults and Intents. This class, we'll call it AGAECC for short, is the primary tool to utilize on the android client side. AGAECC has the ability to handle the user's account selection and handle any issues that go along with provide authorization and such to this app.

Build the Authenticator

Next, you will build the authenticator using the provided, Builder class.

AndroidGAECrossClientAuthenticator.Builder builder = new AndroidGAECrossClientAuthenticator.Builder(new MyServiceAuthenticationListener(), new MyGoogleOAuthIDManager());

Next, call the builder method appropriate to your scenario:

  • #autoQuery(Activity) - After being built, call authenticator#prepareAuthenticator() to automatically pop-up an account chooser dialog. Once chosen, you will handle the outcome in the #onActivityResult method
  • #fromFragment(Fragment) - Same as #autoQuery, except handled within a fragment
  • #fromNonUI(Context, Account) - Utilized from services which will not be querying for an account. In this scenario, the user has already chosen an account and you are simply authenticating this account to the backend.

You can optionally provide an AuthenticatorManager to the builder using #setManager(). By doing so, the authenticator will automatically be added to the Manager once prepared, which will then call the designated listener. See below for details on utilizing the AuthenticationManager.

Prepare the Authenticator

Preparation of the authenticator requires the account token, which is a network operation, and therefore must be completed on a non-UI thread. That is why the AGAECC Authenticator is an AsyncTask. Calling the #prepareAuthenticator() method sets off a chain of events that culminates in calling the provided listener. If the authenticator in question has already been prepared (token available), no network operation is done and the listener is called right away.

If this authenticator does not have an account specified yet, calling the #prepareAuthenticator() method initiates the account picker UI system. This will return to your UI in the form of the #onActivityResult method. In this case, you will want to delegate the result handling to the authenticator to continue the preparation events.

Override the #onActivityResult() method as shown. Essentially, this gives the authenticator first crack at review Activity results, and if the authenticator does not recognize or need to deal with the returned results, it is passed on. The authenticator specifically looks to handle 3 types of results: Account chooser, Authentication Error, or User Recoverable Play Service Error. The authenticator uses default #requestCodes of 3030, 3031, and 3032, but you can override these by calling the appropriate #setRc*() methods on your authenticator.

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
	ServiceAuthenticator authenticator = builder.fromFragment(this).build();
	if (authenticator.onActivityResult(requestCode, resultCode, data)) {
		// TODO You may or may not want to prepare at this point, but the defined authenticator that handles the activity result will have the appropriate account information assigned to it.
		authenticator.prepareAuthentication();
	    return;	
	}
    // TODO Handle other activity results
    super.onActivityResult(requestCode, resultCode, data);
}

Google Sign In (GSI) Authenticator

As of Google Play Services version 8.3, Google has made the GSI system much more developer friendly, and as such, implementing it also provides a more user-friendly experience. While using the GSIA is just as easy (or in some cases easier) than using the AGAECCA, it does have a few more concepts to be aware of that provide some additional power.

Build the Authenticator

Next, you will build the authenticator using the provided, Builder class.

AndroidGSIAuthenticator.Builder builder = new AndroidGSIAuthenticator.Builder(getContext(), new MyServiceAuthenticationListener(), new MyGoogleOAuthIDManager());

Next, call the builder method appropriate to your scenario:

  • #signIn(FragmentActivity) or #signIn(Fragment) - After being built, call authenticator#prepareAuthenticator() from the button click to automatically pop-up an account chooser dialog, if appropriate. If the user is already authenticated for your app, a silent login completes the action. If an account is chosen, you will handle the outcome in the #onActivityResult method (see below).
  • #forAccount(String) - In this case, assuming the user has already been authenticated for your app/service, you can simply get this authenticator up and running silently in order to send requests to the backend
  • #onActivityResult(int, int, Intent) - this method is automatically utilized by to handle responses from account selection (see below)

Prepare the Authenticator

First, you will utilize the pre-built GSI button to present to the user:

<com.google.android.gms.common.SignInButton
            android:id="@+id/prepare_auth"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            />

Your layout should also accomodate for an option of logging out, AKA hide this button and show a log out button.

Second, in your #onCreate and #onCreateView, build your sign in authenticator and link the Sign In button click:

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		AndroidGSIAuthenticator.Builder builder = new AndroidGSIAuthenticator.Builder(getActivity(), new ServiceAuthenticationListener() {

			@Override
			public void onAuthenticatorPrepared(ServiceAuthenticator authenticator) {
				// TODO Proceed with using your authenticator
			}
		}, manager).signIn(this);
		authenticator = builder.build();
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		View root = inflater.inflate(R.layout.fragment_gspagsi, null);
		prepare = (SignInButton) root.findViewById(R.id.prepare_auth);
		prepare.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				authenticator.prepareAuthentication();
			}
		});
		return root;
	}

Third, in your Activity of Fragment class, implement the onActivityResult method as follows:

@Override
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);
		AndroidGSIAuthenticator.Builder builder = new AndroidGSIAuthenticator.Builder(getActivity(), new ServiceAuthenticationListener() {
			@Override
			public void onAuthenticatorPrepared(ServiceAuthenticator authenticator) {
				// TODO Proceed with using your authenticator
			}
		}, manager).onActivityResult(requestCode, resultCode, data);
		authenticator = builder.build();
	}

That handles the Sign-in flow. To provide signout and Disconnect flows, simply call the following methods authenticator#signOut and authenticator#disconnect. The essential difference between the two is the retention of data. Disconnecting is akin to deleting their account, so you should then remove all data pertaining to that user. On the other hand, signing out just removes the current login. However, using this authenticator, you are able to utilize multiple accounts simultaneously (once provided by the user) without requiring a sign out (or account switch).

Authenticator Manager

The AuthenticatorManager is simply a HashMap the tracks available authenticators so that you are not needed to constantly run network operations to prepare an authenticator for each individual RPC. The basic way to utilize this manager is to have your ServiceAuthenticationListener's ready, and you'll pass them to the manager. If the manager has an authenticator available for the specified account, your listener is called immediately. Otherwise it is added to a pending queue which will be called once a prepared authenticator for the account specified has been made available to the manager.

boolean available = authManager.listenFor(Account, ServiceAuthenticationListener);

The return value here tells you if an authenticator was immediately available. In essence, if it return false (not available), you would then Build a ServiceAuthenticator. In this case, simply provide this authManager to the builder#setManager and it will automatically take care of calling your defined listener once prepared.

ServiceAsyncTask Integration

If you make use of the AuthenticationManager mentioned above, you'll be able to leverage this within the ServiceAsyncTask class. Set your manager to your task as the last parameter. Then, when you execute, use the following:

serviceTask.executeForAccount(Account);

This gives you the benefit of of delaying the account utilized until a later time. If an authenticator is not yet available for the account requested, the execution will wait until the manager has one available, and will then execute immediately. Additionally, you can prepare your serviceTask, and choose a the execution point which account to authenticate with (instead of needing to choose the account when you create the serviceTask). Keep an eye out though, this method will wait indefinitely for an authenticator to be provided.

As an additional measure, if your serviceTask requires authentication, override the #requiresAuthenticator method and return true in order catch (at runtime) that you execute with an authenticator (or #executeForAccount).

@Override
public boolean requiresAuthenticator(){
	return true;
}

Test Mode

GSP Requires an HTTPS connection to send the OAuth 2 tokens. During local testing, this is often an un-needed obstacle, so GSP provides a way to override this requirement. DO NOT OVERRIDE UNLESS YOU UNDERSTAND THE SECURITY ISSUES AND VERIFY YOU ARE NOT COMPROMISING THE TOKEN.

In order to override the HTTPS Requirement, your Authenticator must implement the TestModeHostVerifier which essentially allows you to whitelist Safe urls (AKA local). A simple pattern is shown below, where the #getTestModeHostArrayResource() method can be overridden as needed.

	@Override
	public boolean isTestModeHost(URL serviceUrl) {
		String[] whitelist = context.getResources().getStringArray(getTestModeHostArrayResource());
		if (whitelist != null) {
			for (String url : whitelist) {
				if (serviceUrl.getHost().equals(url)) {
					Log.i(LOG_TAG, "Test mode host verified: " + url);
					return true;
				}
			}
		}
		return false;
	}
	
	@ArrayRes
	protected int getTestModeHostArrayResource() {
		return R.array.gsp_no_ssl_whitelist;
	}

The AGAECCA and AGSIA both implement this pattern, so to whitelist a host, simply provide the following in a resource file:

    <string-array name="gsp_no_ssl_whitelist">
        <item>192.168.1.107</item>
    </string-array>