Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refreshing an Access Token for Client Credentials Flow

I was wondering what the best way is for me to refresh an access token that is obtained through the client credentials flow within OAuth 2.0. I've read over the spec, but I can't seem to be able to find an answer for my particular situation.

For my specific case, I am using the Spotify Web API for my Android app in order to search for content (tracks, albums, and/or artists). In order to perform a search, I need an access token. Since I'm not interested in a Spotify user's data, I can use the client credentials flow to obtain the access token, which is explain in Spotify's terms here.

Because the access token can eventually expire, I had to figure out a way to refresh it once expiration occurred. What I'm ultimately wondering is if my approach is valid and if there's any concern with how I've approached this.

First and foremost, I stored my access token within SharedPreferences. Within onCreate(), I have the following:

@Override
protected void onCreate(Bundle savedInstanceState) {
    // A bunch of other stuff, views being initialized, etc.


    mAccessToken = getAccessToken();

    // If the access token is expired, then we will attempt to retrieve a new one
    if (accessTokenExpired()) {
        retrieveAccessToken();
    }
}

I've defined accessTokenExpired() and retrieveAccessToken() as follows:

private boolean accessTokenExpired() {
    // If mAccessToken hasn't yet been initialized, that means that we need to try to retrieve
    // an access token. In this case, we will return true;
    if (mAccessToken == null) {
        return true;
    }

    SharedPreferences preferences = getPreferences(Context.MODE_PRIVATE);
    long timeSaved = preferences.getLong(PREFERENCES_KEY_TOKEN_RESPONSE_TIME_SAVED, 0L);
    long expiration = preferences.getLong(PREFERENCES_KEY_TOKEN_RESPONSE_EXPIRATION, 0L);
    long now = System.currentTimeMillis()/1000;
    long timePassed = Math.abs(now - timeSaved);

    if (timePassed >= expiration) {
        return true;
    } else {
        return false;
    }
}

One thing worth noting about retrieveAccessToken() is that I'm using Retrofit for my HTTP request:

private void retrieveAccessToken() {

    // First, we obtain an instance of SearchClient through our ClientGenerator class
    mClient = ClientGenerator.createClient(SearchClient.class);

    // We then obtain the client ID and client secret encoded in Base64.
    String encodedString = encodeClientIDAndSecret();

    // Finally, we initiate the HTTP request and hope to get the access token as a response
    Call<TokenResponse> tokenResponseCall = mClient.getAccessToken(encodedString, "client_credentials");
    tokenResponseCall.enqueue(new Callback<TokenResponse>() {
        @Override
        public void onResponse(Call<TokenResponse> call, Response<TokenResponse> response) {
            Log.d(TAG, "on Response: response toString(): " + response.toString());
            TokenResponse tokenResponse = null;
            if (response.isSuccessful()) {
                tokenResponse = response.body();
                Log.d(TAG, tokenResponse.toString());
                mAccessToken = tokenResponse.getAccessToken();
                saveAccessToken(tokenResponse);
            }
        }

        @Override
        public void onFailure(Call<TokenResponse> call, Throwable t) {
            Log.d(TAG, "onFailure: request toString():" + call.request().toString());
            mAccessToken = "";
        }
    });
}

Finally, saveAccessToken(tokenResponse) is sort of the complement of accessTokenExpired(), where I'm saving the values from the token response into SharedPreferences rather than retrieving them.

Are there any concerns with how I'm doing this? I got the idea from this SO post and slightly modified it. Spotify doesn't provide a refresh token in their access token response. Therefore, I can't make use of it here to reduce the number of access token requests I make.

Any input on this would be greatly appreciated!

like image 767
coolDude Avatar asked Apr 27 '26 12:04

coolDude


1 Answers

Two considerations are:

  • you probably want some error handling around the requests you make using the access token that can handle the token expiring and do retries. The two situations where this will help are
    1. when the token expires between checking if it's valid and your usage of it
    2. when in the cycle of check the token is valid -> make some requests with the token -> repeat, you spend over an hour using the token. Another way you can do it is to calculate now + expected_api_request_time > token_expiration_time where expected_api_request_time would be a constant you set, but I think handling token expiry as an exception is better practice (you probably want to be able to make retries anyway in cases of network instability).
  • you can perform the calculations to work out when the token expires either when you retrieve the timeSaved and expiration from your local storage, or just calculate the time the token will expire initially and save that. This is relatively minor, both this and the way you've done it are fine I think.
like image 178
Rach Sharp Avatar answered Apr 30 '26 01:04

Rach Sharp