-
Notifications
You must be signed in to change notification settings - Fork 14
Android Authentication 0.6.x
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.
Both authenticators have some common points. Both require GoogleOAuthIdManager
s and ServiceAuthenticationListener
s. 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.
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.
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.
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, callauthenticator#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.
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 #requestCode
s 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);
}
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.
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, callauthenticator#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)
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).
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.
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;
}
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>