Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Login in twice when using SyncAdapters

I am creating a new Android app using SyncAdapter to handle db sync. I have everything in place and the app is working fine but I noticed that I am logged in twice.

The first login takes place when the AuthenticatorActivity class (it extends AccountAuthenticatorActivity) validates the user and password.

If the user and password are correct the AuthenticatorActivity then does:

  • If the account didn't exist it creates it using mAccountManager.addAccountExplicitly()
  • The authToken is saved using intent.putExtra(AccountManager.KEY_AUTHTOKEN, authToken);

This was basically copied/pasted from the Android samples, so I guess it's ok.

The issue is that when the SyncAdapter launches and uses

authtoken = mAccountManager.blockingGetAuthToken(account,
          AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE, true);

The getAuthToken() method inside the Authenticator class which extends AbstractAccountAuthenticator is called. And inside this method I am hitting the login endpoint once again.

From that point onwards the login endpoint is not hit again until the authToken expires.

This is not something that bothers me a lot but I would like to know if there is a way to avoid doing the login twice.

like image 470
Macarse Avatar asked Jul 11 '12 14:07

Macarse


1 Answers

As you've seen, though Authenticator.java in the SampleSyncAdapter says

The interesting thing that this class demonstrates is the use of authTokens as part of the authentication process. ... If we already have an authToken stored in the account, we return that authToken. If we don't, but we do have a username and password, then we'll attempt to talk to the sample service to fetch an authToken.

that's a lie. Authenticator.getAuthToken doesn't to any cache checking, it just hits the network to get a token.

The solution is to add in the missing check:

Authenticator.java:
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
        String authTokenType, Bundle loginOptions) throws NetworkErrorException {

    // check that authToken type supported
    ...

    // Check if we already have a cached token to return
    final AccountManager am = AccountManager.get(mContext);
    String cachedAuthToken = am.peekAuthToken(account, authTokenType);
    if (cachedAuthToken != null) {
        final Bundle result = new Bundle();
        result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
        result.putString(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
        result.putString(AccountManager.KEY_AUTHTOKEN, cachedAuthToken);
        return result;
    }

    // Get new authToken from server
    ...

    // If all else fails, prompt the user for credentials
    ...
}

Note that the rest of your project needs to religiously use AccountManager.invalidateAuthToken when calls fail or else you'll wind up with an infinite loop of calls failing, trying to get a new auth token, and then failing again when the same cached auth token is returned.

like image 173
blahdiblah Avatar answered Nov 15 '22 13:11

blahdiblah