Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use refresh token if auth token expired in account authenticator

I have an app which uses AccountManager to store users' accounts. Users log in and sign up through my REST API using OAuth2.0 password-username credentials flow.

The access tokens that users receive expire in 2 hours and need to be refreshed until expired again, and so on.

I need to implement this refreshing functionality in my authenticator.

I have a model called AccessToken which has the following fields:

String accessToken, String tokenType, Long expiresIn, String refreshToken, String scope, Long createdAt.

So currently, in my getAuthToken method in AccountAuthenticator class I receive this AccessToken object and use its accessToken field as an Auth Token for my Account Manager.

What I need is to somehow store both refresh token and auth token using my account manager and when the app tries to access the API and gets the error response: {"error": "access token expired"}, to refresh the current access token using the refreshToken string from the AccessToken object received previously. However, I'm not sure how I should do it.

My getAuthToken method in authenticator class currently looks like this:

@Override
public Bundle getAuthToken(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account,
                           String authTokenType, Bundle options) throws NetworkErrorException {
    if (!authTokenType.equals(AccountGeneral.AUTHTOKEN_TYPE_READ_ONLY) &&
            !authTokenType.equals(AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS)) {
        final Bundle result = new Bundle();
        result.putString(AccountManager.KEY_ERROR_MESSAGE, "Invalid authTokenType");
        return result;
    }

    final AccountManager manager = AccountManager.get(context.getApplicationContext());

    String authToken = manager.peekAuthToken(account, authTokenType);

    Log.d("Discounty", TAG + " > peekAuthToken returned - " + authToken);

    if (TextUtils.isEmpty(authToken)) {
        final String password = manager.getPassword(account);
        if (password != null) {
            try {
                authToken = discountyService.getAccessToken(DiscountyService.ACCESS_GRANT_TYPE,
                        account.name, password).toBlocking().first().getAccessToken();
// =======
// Here the above discountyService.getAccessToken(...) call returns
// AccessToken object on which I call the .getAccessToken() 
// getter which returns a string.
// =======
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    if (!TextUtils.isEmpty(authToken)) {
        final Bundle result = new Bundle();
        result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
        result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
        result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
        return result;
    }

    final Intent intent = new Intent(context, LoginActivity.class);
    intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, accountAuthenticatorResponse);
    intent.putExtra(LoginActivity.ARG_ACCOUNT_TYPE, account.type);
    intent.putExtra(LoginActivity.ARG_AUTH_TYPE, authTokenType);
    final Bundle bundle = new Bundle();
    bundle.putParcelable(AccountManager.KEY_INTENT, intent);
    return bundle;
}

and the class AccountGeneral just contains some constants:

public class AccountGeneral {

    public static final String ACCOUNT_TYPE = "com.discounty";

    public static final String ACCOUNT_NAME = "Discounty";

    public static final String AUTHTOKEN_TYPE_READ_ONLY = "Read only";

    public static final String AUTHTOKE_TYPE_READ_ONLY_LABEL = "Read only access to a Discounty account";

    public static final String AUTHTOKEN_TYPE_FULL_ACCESS = "Full access";

    public static final String AUTHTOKEN_TYPE_FULL_ACCESS_LABEL = "Full access to a Discounty account";
}

The app will also be using SyncAdapter and will interact with the API quite frequently to sync the data from and to the server, and these API calls will also need to use access tokens as parameters in the requests, so I really need to implement this refreshing functionality and make it automatic.


Does anybody know how to implement that correctly?


PS: I'll be using a local database to store all my data and I could store the token objects as well. This seems like an easy hack, although insecure. Maybe I should always store just one refresh token at a time as a db record and update it as the app receives the new token?

PPS: I'm free to change the way the API works anyhow, so if there are suggestions on improving the mobile app by making the API better, they are very appreciated as well.

like image 935
Denis Yakovenko Avatar asked Dec 30 '15 11:12

Denis Yakovenko


People also ask

What happens when auth token expires?

When the access token expires, the application will be forced to make the user sign in again, so that you as the service know the user is continually involved in re-authorizing the application.

Can I use refresh token instead of access token?

Refresh Token are typically longer lived than Access Tokens and used to request a new Access Token without forcing user authentication. Unlike Access Tokens, Refresh Tokens are only used with the Authorization Server and are never sent to a web service.

How do I update my access token with refresh token?

Using a Refresh Token These client credentials and the refresh_token can be used to create a new value for the access_token . To refresh the access token, select the Refresh access token API call within the Authorization folder of the Postman collection. Next, click the Send button to request a new access_token .

When should I refresh my access token?

When to use Refresh Tokens? The main purpose of using a refresh token is to considerably shorten the life of an access token. The refresh token can then later be used to authenticate the user as and when required by the application without running into problems such as cookies being blocked, etc.


1 Answers

You can save the refresh token into your account's user data when you first add your account using:

Bundle userdata = new Bundle;
userdata.putString("refreshToken", refreshToken);
mAccountManager.addAccountExplicitly (account, password, userdata);

You can also set the userdata after adding the account by calling:

mAccountManager.setUserData(account, "refreshToken", refreshToken);

When you access token is expired you can retrieve your refresh token by calling:

String refreshToken = mAccountManager.getUserData(account, "refreshToken");

Use the refreshToken to retrieve a new access token.

like image 62
Grace Coder Avatar answered Oct 12 '22 09:10

Grace Coder