My app only stores user/pass. No tokens are used.
Methods setAccountAuthenticatorResult(Bundle)
and onResult(Bundle)
are meant to notify the AbstractAccountAuthenticator
about the outcome, but I have a project working without them, what are they for ?
What is onRequestContinued()
for ?
When addAccount
is finished and the account created, should onActivityResult
be called on the Activity
that triggered it?
If an Intent
is returned with key AccountManager.KEY_INTENT
in addAccount
implementation, the AbstractAccountAuthenticator
will start the Intent
. I have noticed that many developers add extras. Who gets them ?
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException
{
Intent intent = new Intent(mContext, AuthenticatorActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE, accountType); // <-- this
intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType); // <-- this
intent.putExtra(AuthenticatorActivity.ARG_IS_ADDING_NEW_ACCOUNT, true); // <-- this
Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
kris larson:
Thanks for the answer. I think we might be using the AccountManager
wrong to be honest.
We want to share some credentials across our apps, so we have a Service
to hold the custom account type. Since apps know the account type and share the signing certificate, they have access to the Account
.
When each app is started, they try to get the Account
. If no Account
exists, they trigger the login page in our Service
by calling AccountManager.addAccount(...)
.
Once the login (through web services) is successfull, we make it available to other apps with AccountManager.addAccountExplicitly(...)
. Not setting an outcome after it, does not impact the outcome.
How does it affect the AccountManager
? Is this approach correct ?
EDIT: I'll just describe how — after much agony — I implemented the authentication service for our app.
You said you have a Service
for the authenticator. I'll assume you did that in the prescribed way: declare a service in your manifest that references your class, which subclasses android.accounts.AbstractAccountAuthenticator
. The manifest entry also references an XML file with the account-authenticator
tag that declares your account type, plus a name and icons to be used by the Settings | Accounts page. That stuff is all documented.
For our app, I wanted the last signed in user to sign in automatically until they selected "Sign Out" on the navigation drawer. To accomplish this, the username would be persisted in SharedPreferences
for the app.
So if the SharedPreferences
did not have a username, the app calls AccountManager.newChooseAccountIntent()
for our custom account type, then calls startActivityForResult
with intent that was returned. If the user selects "Add new account", then addAccount()
will be invoked on the authenticator as described below.
Our authentication activity not only has UI for username/password login but also password reset and new trial account creation, so there are a lot of moving parts. When the user enters the username/password and presses the Sign In button, the app authenticates against our server. If successful, the app calls AccountManager.addAccountExplicitly()
for that username.
The AccountManager
Choose Account activity will call onActivityResult()
when this is all finished. If we get RESULT_OK
the app persists the username for the next sign in operation.
If the SharedPreferences
has a username, and the Account Manager has an account registered for it, we don't need to select anything and just skip to this next phase.
With the account selected/known, now the app can authenticate. It calls peekAuthToken()
to see if a token was registered, and invalidateAuthToken()
if it was. This is done so that when the app calls getAuthToken()
on the account manager, it forces the account manager to call getAuthToken()
on the app's authenticator to authenticate with the server. Since the server is not returning tokens, we are using dummy tokens and that is why they are invalidated every time.
Now the interesting part about all this is that if the user selects an account that was already registered, it won't be authenticated, whereas if they selected Add New Account and that operation was successful, the new account will be authenticated. So just be aware that if you notice that signing into a new account causes two round trips to your authentication server, now you know why. (I have some logic in there using setUserData()
on the account to indicate pre-authenticated, but it looks like I forgot to finish that feature. Hmmm.)
Let's start with some background to clarify things.
You have some subclass of AbstractAccountAuthenticator
. You have created this class to respond to authentication requests for the account type it was registered for. The thing to keep in mind about this class is that the platform AccountManager
is always the component your app will interact with; your app will never interact directly with this class. So in a sense the AccountManager
is a client of your authentication service. The work that the authenticator performs is stuff that will be in a background thread.
Now part of the work the authentication service must do is interact with the user, asking for user account names, passwords, fingerprint IDs, smart cards, etc. So let's assume you have an AuthenticatorActivity
that does that work. Typically you will subclass android.accounts.AccountAuthenticatorActivity
for this. This activity class is really nothing special. The only thing it does is to expect a reference to an AccountAuthenticatorResponse
when it starts, then to call that response's onResult()
method when the activity exits.
The app I work on is similar to yours in that there is no token returned from the authentication service. I still implemented getAuthToken
for two reasons:
So let's follow the bouncing ball to understand how everything fits together.
AccountManagerCallback
interface in order to get asynchronous messages from the AccountManager
.Account
object with this username.AccountManager.getAccountsByType()
to ensure that the account exists.AccountManager.getAuthToken()
for the account.AccountManager
sees that your authenticator is registered for that account type and calls authenticator's getAuthToken
method.getAuthToken
method will contact your authentication server and get a response.If the login fails, then things get interesting.
Since your authenticator is not returning an auth token, it must return an intent to start your AuthenticatorActivity
so that the user can re-authenticate.
The AccountManager
uses your intent to start your AuthenticatorActivity
. As part of the request, it will get a reference to an AccountAuthenticatorResponse
it will need later.
AuthenticatorActivity
interacts with the user, gets username/password, contacts your server, gets a response. Let's say the authentication succeeds.onResult
method of the AccountAuthenticatorResponse
it was given.AccountManager
on getting notification from the response object will invoke the callback method with the results.So with that we can answer your questions:
Methods
setAccountAuthenticatorResult(Bundle)
andonResult(Bundle)
are meant to notify theAbstractAccountAuthenticator
about the outcome, but I have a project working without them, what are they for ?
More correctly, they are meant to notify the AccountManager
about the outcome.
Are you sure it's working? Have you tried logging in with an invalid username/password? AccountManager
will assume authentication has been cancelled unless it gets notified otherwise.
What is
onRequestContinued()
for ?
I don't have an exact answer. My guess is that it's to keep the AccountManager
in sync somehow with what is happening with the UI in regards to authentication.
If an
Intent
is returned with keyAccountManager.KEY_INTENT
inaddAccount
implementation, theAbstractAccountAuthenticator
will start the Intent. I have noticed that many developers add extras. Who gets them ?
The answer is: You do. This intent will go the AuthenticatorActivity
that you have created to handle authentication, so any data your activity needs to perform authentication needs to be passed in with these extras.
When
addAccount
is finished and the account created, shouldonActivityResult
be called on theActivity
that triggered it?
The use case for this is when your app calls AccountManager.newChooseAccountIntent()
then calls startActivityForResult()
with the resulting intent. The AccountManager
's "choose account" activity is then started.
Now since "add new account" is an option on the "choose account" UI, the AccountManager
will call your authenticator's addAccount()
method when that option is selected. Your authenticator then returns a Bundle
as described in the AbstractAccountAuthenticator
docs back to AccountManager
. After this the "choose account" activity will finish and call onActivityResult()
on your activity.
Hopefully you're starting to see that the AccountManager
acts as a broker between your app and your authenticator components. To make this clearer, consider that you could package up those components in a way that other apps could use them to authenticate to your service without knowing any details. If you created your own custom account type, you can invoke your authentication directly from the Settings | Accounts page of the device. This is a good test to see if your addAccount()
method is implemented correctly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With