I built an AccountAuthenticator for a webservice that I\'d like to use in other applications with different signatures. I\'d like to instantly show the fullscreen access req
There isn't a good tutorial for this workflow but I was finally able to get this to work in my app. I am adding this answer for posterity for anyone else coming across this particular use case.
Running as a Background Task
manager.getAuthToken(account, "full", null, true, callback, new Handler());
The above expects you to be running in some background task where you don't need to display UI right away. The java doc for this method explains this. Sadly, if you use a "false" flag you don't get a callback that the SecurityException occurred.
Running in the Foreground
manager.getAuthToken(account, "full", null, activity, callback, new Handler());
This expects the app calling this method to share the same signing certificate of the authenticator. This is where I and many people are getting hung up. I tried a few versions of android from 4.0.1 onward to 5.0 and it seems to be intended behavior and not a bug in their code.
The Solution
Use a different means of getting the auth token. specifically confirm credentials of the account you need.
mgr.confirmCredentials(account, null, activity, callback, null);
By supporting Confirm Credentials you can send an intent to launch an AccountAuthenticatorActivity through the Authenticator and avoid the security exception. You have full control over the UI here so while normally you could request a new password for the account, you could instead show a page with a button to confirm that the account should be used in the calling application. Then, all you have to do is send the auth token in the AccountManagerCallback bundle going back to the calling app.
Example Callback Method
new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> bundle) {
boolean success = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
if (success) {
final String token = result.getString(AuthenticationHelper.ACCESS_TOKEN);
//cache token somewhere local
}
}
}
You are using wrong method. Have a look at documentation:
- getAuthToken rising notification:
- getAuthToken prompting the user for credentials if necessary
Example of a method you are looking for:
Bundle options = new Bundle();
accountManager.getAccountManager().getAuthToken(account, SyncUtils.AUTH_TOKEN_TYPE, options, Accounts.this, new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> future) {
Bundle bundle = bundle = future.getResult();
if(bundle == null)
return;
if (bundle.containsKey(AccountManager.KEY_INTENT)) {
Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(intent, SyncUtils.REQUEST_AUTHENTICATE);
} else if (bundle.containsKey(AccountManager.KEY_AUTHTOKEN)) {
...
credential.setAccessToken(bundle.getString(AccountManager.KEY_AUTHTOKEN));
setUI();
}
}
}, null);
This appears to be a bug (or security feature) in KitKat, preventing cross app token sharing. I recommend you implement token sharing using your own custom intents and security validation, rather than relying on Android's APIs.