Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android OAuth2 Bearer token best practices

This nice tutorial is a very good introduction to account authentication on Android and doing it by utilizing Android's AccountManager.

However, I need to create a client app for an OAuth2 API using a Bearer token for authentication. At the time of obtaining the token, I receive the expiry timestamp for it, but I am unclear about where to store and how to make use of it properly. Problem is, if I don’t want to have unnecessary trips to the server, the app would realize that the Bearer had become invalid only after it receives a HTTP 401 error from the server when requesting any random resource. So, what is the best practice to tackle this:

  1. Should every network request in my code have a retry mechanism in case the bearer token has become invalid in meantime? I would probably invalidateAuthToken when catching the exception and retry.
  2. Can Sync Adapter somehow help here?

As I am new to Android development, I expect that the solution may also be something completely different than I expect.

If it is relevant, I intend to use Volley for the server communication.

like image 940
nekojsi Avatar asked Dec 17 '13 18:12

nekojsi


2 Answers

I found out my own answers after a bit of investigation:

  1. Yes, calling AccountManager#invalidateAuthToken removes the last saved authentication token (access token in the OAuth2 case) and expects that you are detecting that on the next AccountAuthenticator#getAuthToken call. For example, the following is my code for that method:

    @Override
    public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
        // Extract the username and password from the Account Manager, and ask
        // for an appropriate AuthToken.
        final AccountManager am = AccountManager.get(mContext);
    
        String authToken = am.peekAuthToken(account, authTokenType);
    
        // EXTRA: I am also storing the OAuth2 refresh token in the AccountManager
        Map<String, String> refreshResult = null;
        String refreshToken = am.getUserData(account, KEY_REFRESH_TOKEN);
        if (TextUtils.isEmpty(authToken) && !TextUtils.isEmpty(refreshToken)) {
            // lets try to refresh the token
            // EXTRA: AuthenticationProvider is my class for accessing the authentication server, getting new access and refresh token based on the existing refresh token
            refreshResult = AuthenticationProvider.
                refreshAccessToken(am.getUserData(account, KEY_REFRESH_TOKEN));
        }
    
        // If we get a result from the refresh - we return it
        if (!refreshResult.isEmpty()) {
            authToken = refreshResult.get(AccountManager.KEY_AUTHTOKEN);
            // EXTRA: new refresh token used only in OAuth2
            refreshToken = refreshResult.get(KEY_REFRESH_TOKEN);
    
            final Bundle result = new Bundle();
            result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
            result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
    
            // store the new tokens in the system
            am.setAuthToken(account, authTokenType, authToken);
            am.setUserData(account, KEY_REFRESH_TOKEN, refreshToken);
    
            result.putString(AccountManager.KEY_AUTHTOKEN, refreshResult.get(AccountManager.KEY_AUTHTOKEN));
            result.putString(KEY_REFRESH_TOKEN, refreshResult.get(KEY_REFRESH_TOKEN));
            return result;
        }
    
        // If we get here, then we couldn't access the user's password - so we
        // need to re-prompt them for their credentials. We do that by creating
        // an intent to display our AuthenticatorActivity.
        final Intent intent = new Intent(mContext, LoginActivity.class);
        intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
        final Bundle bundle = new Bundle();
        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
        return bundle;
    }
    

    I also received a confirmation from the author of the blog post mentioned in the question.

  2. SyncAdapters cannot help directly, as their true purpose is obtaining data from network asynchronously (for the developer) and transparently (for the user). They just use AbstractAccountAuthenticator and call its methods where appropriate.

like image 52
nekojsi Avatar answered Sep 20 '22 14:09

nekojsi


I was going through the same problem before and make this library to handel OAuth2 in Android. but the library is an extension for Retrofit that simplifies the process of authenticating against an OAuth 2 provider but still you can use it.

like image 20
abozaid Avatar answered Sep 21 '22 14:09

abozaid