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 usingDagger
+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 insideTokenAuthenticator
class. For example when your activity and your service make requests concurrently. To beat this issue just addsynchronized
keyword to yourTokenAuthenticator
sauthenticate
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 useSchedulers.trampoline()
orblockingGet()
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 onTokenAuthenticator
.
This is what we are trying to achieve:
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
Google Map Signed API Key Errors in Android
Why Extend the Android Application Class
Changing Screen Brightness Programmatically (As with the Power Widget)
How to Change Default Dialog Button Text Color in Android 5
Android Service Stops When App Is Closed
What to Use Instead of "Addpreferencesfromresource" in a Preferenceactivity
How to Keep the Screen on in My App
Android: Access Child Views from a Listview
How to Refresh Activity After Changing Language (Locale) Inside Application
Pass a String from One Activity to Another Activity in Android
Android: Keeping a Background Service Alive (Preventing Process Death)
How to Provide Shadow to Button
Android Canvas: Drawing Too Large Bitmap
How to Achieve Ripple Animation Using Support Library