Skip to content

Instantly share code, notes, and snippets.

@alex-shpak
Last active June 14, 2024 02:40
Show Gist options
  • Save alex-shpak/da1e65f52dc916716930 to your computer and use it in GitHub Desktop.
Save alex-shpak/da1e65f52dc916716930 to your computer and use it in GitHub Desktop.
Refreshing OAuth token with okhttp interceptors. All requests will wait until token refresh finished, and then will continue with the new token.
private class HttpInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//Build new request
Request.Builder builder = request.newBuilder();
builder.header("Accept", "application/json"); //if necessary, say to consume JSON
String token = settings.getAccessToken(); //save token of this request for future
setAuthHeader(builder, token); //write current token to request
request = builder.build(); //overwrite old request
Response response = chain.proceed(request); //perform request, here original request will be executed
if (response.code() == 401) { //if unauthorized
synchronized (httpClient) { //perform all 401 in sync blocks, to avoid multiply token updates
String currentToken = settings.getAccessToken(); //get currently stored token
if(currentToken != null && currentToken.equals(token)) { //compare current token with token that was stored before, if it was not updated - do update
int code = refreshToken() / 100; //refresh token
if(code != 2) { //if refresh token failed for some reason
if(code == 4) //only if response is 400, 500 might mean that token was not updated
logout(); //go to login screen
return response; //if token refresh failed - show error to user
}
}
if(settings.getAccessToken() != null) { //retry requires new auth token,
setAuthHeader(builder, settings.getAccessToken()); //set auth token to updated
request = builder.build();
return chain.proceed(request); //repeat request with new token
}
}
}
return response;
}
private void setAuthHeader(Request.Builder builder, String token) {
if (token != null) //Add Auth token to each request if authorized
builder.header("Authorization", String.format("Bearer %s", token));
}
private int refreshToken() {
//Refresh token, synchronously, save it, and return result code
//you might use retrofit here
}
private int logout() {
//logout your user
}
}
@alex-shpak
Copy link
Author

@alouanemed

Are we talking here about Server Token(sent by backend) or say Facebook Oauth Token ? Thanks.

About server token, I think facebook has their own means to update tokens.

@rajeshkumarkhadka

How can we make refreshing access token block to execute only one time without using synchronized to okhttp instance. Can we make synchronized to other instance?

I think you can use some generic object for locks, but I can't be sure that it will work correct

@jaswanthm
Copy link

I have implemented this without any fuss but there is a talk that Authenticator is a better of doing it. Thoughts anyone ?

https://github.com/square/okhttp/wiki/Recipes#handling-authentication

@luckcoolla
Copy link

Authenticator is completely suitable tool for the token refresh action and retry the main request

@peterlazar1993
Copy link

@alex-shpak Can you give a hint on how the settings object is accessed and updated? I am struggling to understand how to update the persistence layer once the new token is generated.

@CarolusX74
Copy link

@alex-shpak thanks for sharing your knowledge. Base on your model I've built mine own. I added few details, as synchronous refresh token and so on. This is it https://gist.github.com/CarlosJTorres/057dbf4d8de3095873b15956bdf47935 . Thank you very much.

@lalitsonawane
Copy link

@alex_shpak i have a secured json link with https. Hiw to handle it?

@alex-shpak
Copy link
Author

alex-shpak commented Jul 26, 2017

@jaswanthm that was quite long time ago, so I don't remember why Authenticator didn't work for us. I think it was related to paralel requests.

@peterlazar1993 there are no specific requirements on persistence, the simplest I think is to use SharedPreferences

SharedPreferences settings = ...
String token = settings.getString("token")

settings.edit()
    .putString("token", newToken)
    .commit()

@lalitsonawane can you please provide more details? From what I understood you want to update token via https.
You can use Retrofit or Okhttp to call endpoint and refresh token, note that call should be synchronous.

@akshay2211
Copy link

Thnks @alex-shpak, Helped me out alot.

@Agrahyah
Copy link

Hi, suppose i want to inject my SharedPreference (where my current token is stored) using dagger. How to do so ??

@ilhamsuaib
Copy link

ilhamsuaib commented Oct 25, 2017

my case is token not pushed using header like above, but token is always include in every request as parameter.
and if i want to refresh token, i should call refresh token service with POST method. how can i do that @alex-shpak?

@paragones
Copy link

Is there a way to do it with observable (rx java)?

@paragones
Copy link

paragones commented Dec 18, 2017

I need to do it something like this
private class AuthInterceptor(val auth: AuthenticationComponent) : Interceptor {
@throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
val originalRequest = chain?.request()!!

        val token = auth.token()

        val newRequest = originalRequest.newBuilder()
                .header("Authorization", "JWT $token")
                .build()

        val response = chain.proceed(newRequest)

        if (response.code() != 200) {
            auth.refreshToken()
                    .subscribe {   }
        } else {
            return response
        }

        Log.e(this.javaClass.simpleName, "response ${response.code().toString()}")
        Log.e(this.javaClass.simpleName, "response.body() ${response.body()}")
        Log.e(this.javaClass.simpleName, "response.headers() ${response.headers()}")

    }
}

auth.refreshToken() is an rxjava call

@Kolyall
Copy link

Kolyall commented Feb 6, 2018

@jaswanthm how to implement refresh token with Authenticator but avoid multiply token updates from different threads?

@andrconstruction
Copy link

Hi.How do you implement the logout function within the interceptor? is The intercpetor standalone class or a part of an activity?

@alex-shpak
Copy link
Author

@Kolyall
In example synchronized block is used to avoid multiple tokens updates.

@andrconstruction
What kind of logout you need to implement?
Usually it's enough to "forget" api key (and then to not send it to server)

@roni-castro
Copy link

roni-castro commented Dec 5, 2018

@paragones Did you find a way to refresh the token using rxjava?

@maxim-petlyuk
Copy link

@paragones Did you find a way to refresh the token using rxjava?

https://gist.github.com/mpetlyuk/77ac3221c1776d14654374faf2985d5a
look at my implementation. Include my transformer into each request observable via mehtod Observable.compose(/* refresh transformer*/)

@Tgo1014
Copy link

Tgo1014 commented Dec 5, 2019

Very useful. Thanks!

@silwar
Copy link

silwar commented Jan 12, 2021

This is very good. Thanks for sharing this.
Only issue with this implementation is after token gets refreshed, all other requests get executed in serial.
Is there a way to make execution of those requests parallel?

@mecoFarid
Copy link

private int refreshToken() {
		//Refresh token, synchronously, save it, and return result code
		//you might use retrofit here
}

So you are proposing to use retrofit inside Interceptor to refresh token? This is quite ugly workaround.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment