Refreshing Oauth Token Using Retrofit Without Modifying All Calls

Refreshing OAuth token using Retrofit without modifying all calls

Please do not use Interceptors to deal with authentication.

Currently, the best approach to handle authentication is to use the new Authenticator API, designed specifically for this purpose.

OkHttp will automatically ask the Authenticator for credentials when a response is 401 Not Authorised retrying last failed request with them.

public class TokenAuthenticator implements Authenticator {
@Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken();

// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}

@Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
// Null indicates no attempt to authenticate.
return null;
}

Attach an Authenticator to an OkHttpClient the same way you do with Interceptors

OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(authAuthenticator);

Use this client when creating your Retrofit RestAdapter

RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(ENDPOINT)
.setClient(new OkClient(okHttpClient))
.build();
return restAdapter.create(API.class);

Android Retrofit 2.0 Refresh Tokens

I searched this topic since 2-3 months ago and found OkHttp's Authenticator. You can use it. There is one link here: refreshing-oauth-token-using-retrofit-without-modifying-all-calls

It works like that: If your request returns 401, then Authenticator moves in, and refreshes your token. But don't forget to return null or put any try limit. If you don't limit, it will try to refresh multiple times when your refresh request fails. Also, make synchronous requests when refreshing your token.

Also, I have a question and answer -both written by myself- about refreshing the Oauth2 token:

Question: android-retrofit2-refresh-oauth-2-token

Answer: android-retrofit2-refresh-oauth-2-token-answer

Additionally: For example if you have a token and you need to refresh it per 3 hours. You can write an Interceptor too. In Interceptor: compare time and refresh your token without getting any 401 response.

Square's documentation for Interceptor: OkHttp Interceptors

Square's documentation for Authenticator: OkHttp handling-authentication

I know there is no code here, but see links and edit your question then I will try to help you.

Is there a method to refresh token with timer using retrofit?

You can use a Worker and set it to run every 30min or so and set it to save the renewed token in your SharedPreference

here's an example for the Worker

class UpdateTokenWorkManger(
val context: Context,
params: WorkerParameters) : Worker(context, params) {

override fun doWork(): Result {

LoginHandler.refreshTokenSilently()

// Indicate whether the work finished successfully with the Result
return Result.success()
}

companion object {
private const val TAG = "Token Refresh "
const val TOKEN_REFRESH_WORK_MANGER_ID = "automatic_renew_token_work_manger"
fun renewToken() {
val periodicRefreshRequest = PeriodicWorkRequest.Builder(
UpdateTokenWorkManger::class.java, // Your worker class
30, // repeating interval
TimeUnit.MINUTES
)

val periodicWorkRequest: PeriodicWorkRequest = periodicRefreshRequest
.build()
WorkManager.getInstance(App.getApplication()).enqueueUniquePeriodicWork(
TOKEN_REFRESH_WORK_MANGER_ID,
ExistingPeriodicWorkPolicy.REPLACE,
periodicWorkRequest
)
}
}

to use this component you will need these dependencies

implementation "androidx.work:work-runtime-ktx:2.4.0"

also note that LoginHandler is the class that should be responsible for handling your login, refresh and logout scenarios.

and don't forget to add this line to your first Activity after the login Activity, for example: if you login in SplashActivity and after succesful authentication you redirect to MainActivity, then this line should be in MainActivity's onCreate function

UpdateTokenWorkManger.renewToken()

Android Retrofit2 Refresh Oauth 2 Token


Disclaimer :
Actually I am using Dagger +RxJava + Retrofit but I just wanted to provide an answer to demonstrate logic for future visitors.

Important :
If you are making requests from several places your token will refresh multiple times inside TokenAuthenticator class. For example when your activity and your service make requests concurrently. To beat this issue just add synchronized keyword to your TokenAuthenticators authenticate method.

Please make synchronous requests when refreshing your token inside Authenticator because you must block that thread until your request finishes, otherwise your requests will be executed twice with old and new tokens.
You can use Schedulers.trampoline() or blockingGet() when refreshing your token to block that thread.

Also inside authenticate method you can check if token is already refreshed by comparing request token with stored token to prevent unnecessary refresh.

And please do not consider using TokenInterceptor because it is edge case and not for everyone, just focus on TokenAuthenticator.

This is what we are trying to achieve:

Sample Image

First of all refreshing token is a critical process for most apps.
The flow is: If refresh token fails, logout current user and require to re-login. (Maybe retry refresh token couple of times before logging out the user)

Anyways I will explain it step by step:

Step 1: Please refer singleton pattern, we will create one class that's responsible for returning our retrofit instance. Since it is static if there is no instance available it just creates instance only once and when you call it always returns this static instance. This is also basic definition of Singleton design pattern.

public class RetrofitClient {

private static Retrofit retrofit = null;

private RetrofitClient() {
// private constructor to prevent access
// only way to access: Retrofit client = RetrofitClient.getInstance();
}

public static Retrofit getInstance() {
if (retrofit == null) {
// TokenAuthenticator can be singleton too
TokenAuthenticator tokenAuthenticator = new TokenAuthenticator();

// !! This interceptor is not required for everyone !!
// Main purpose of this interceptor is to reduce server calls

// Our token needs to be refreshed after 10 hours
// We open our app after 50 hours and try to make a request.
// Of course token is expired and we will get a 401 response.
// So this interceptor checks time and refreshes token beforehand.
// If this fails and I get 401 then my TokenAuthenticator does its job.
// if my TokenAuthenticator fails too, basically I just logout the user.
TokenInterceptor tokenInterceptor = new TokenInterceptor();

OkHttpClient okClient = new OkHttpClient.Builder()
.authenticator(tokenAuthenticator)
.addInterceptor(tokenInterceptor)
.build();

retrofit = new Retrofit.Builder()
.baseUrl(base_api_url)
.client(okClient)
.build();
}
return retrofit;
}
}

Step 2: In my TokenAuthenticator's authenticate method :

@Override
public synchronized Request authenticate(Route route, Response response) throws IOException {

boolean refreshResult = refreshToken();
if (refreshResult) {
// refresh token is successful, we saved new token to storage.
// Get your token from storage and set header
String newaccesstoken = "your new access token";

// execute failed request again with new access token
return response.request().newBuilder()
.header("Authorization", newaccesstoken)
.build();

} else {
// Refresh token failed, you can logout user or retry couple of times
// Returning null is critical here, it will stop the current request
// If you do not return null, you will end up in a loop calling refresh
return null;
}
}

And refreshToken method, this is just an example you can create your own:

public boolean refreshToken() {
// you can use RxJava with Retrofit and add blockingGet
// it is up to you how to refresh your token
RefreshTokenResult result = retrofit.refreshToken();
int responseCode = result.getResponseCode();

if(responseCode == 200) {
// save new token to sharedpreferences, storage etc.
return true;
} else {
//cannot refresh
return false;
}
}

Step 3: For those who wants to see TokenInterceptor logic:

public class TokenInterceptor implements Interceptor {
SharedPreferences prefs;
SharedPreferences.Editor prefsEdit;

@Override
public Response intercept(Chain chain) throws IOException {

Request newRequest = chain.request();

// get expire time from shared preferences
long expireTime = prefs.getLong("expiretime",0);
Calendar c = Calendar.getInstance();
Date nowDate = c.getTime();
c.setTimeInMillis(expireTime);
Date expireDate = c.getTime();

int result = nowDate.compareTo(expireDate);
// when comparing dates -1 means date passed so we need to refresh token
if(result == -1) {
//refresh token here , and get new access token
TokenResponse tokenResponse = refreshToken();

// Save refreshed token's expire time :
integer expiresIn = tokenResponse.getExpiresIn();
Calendar c = Calendar.getInstance();
c.add(Calendar.SECOND,expiresIn);
prefsEdit.putLong("expiretime",c.getTimeInMillis());

String newaccessToken = "new access token";
newRequest=chain.request().newBuilder()
.header("Authorization", newaccessToken)
.build();
}
return chain.proceed(newRequest);
}
}

I am making requests at activities and background services. All of them uses the same retrofit instance and I can easily manage access token. Please refer to this answer and try to create your own client. If you still have issues simply comment below, I'll try to help.



Related Topics



Leave a reply



Submit