Android OAuth2 Bearer token best practices
Asked Answered
S

2

10

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.

Subjectivism answered 17/12, 2013 at 18:41 Comment(0)
S
10

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.

Subjectivism answered 28/1, 2014 at 13:12 Comment(2)
Can any one point me out how secure it, when using account manager to persist auth token and refresh token ? We have came across bunch of security auditors who says account manager is not safe to keep the token details. But I cant find a proper way to keep them anywhere else.Wrong
@sahan, everything is stored in plaintext in account manager, but it's only accessible on a rooted device. The point of tokens is to allow for revocable authentication without exposing the users true login details, you shouldn't worry about how you store them on the device.Honaker
R
1

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.

Rockrose answered 1/10, 2015 at 12:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.