175

We are using Retrofit in our Android app, to communicate with an OAuth2 secured server. Everything works great, we use the RequestInterceptor to include the access token with each call. However there will be times, when the access token will expire, and the token needs to be refreshed. When the token expires, the next call will return with an Unauthorized HTTP code, so that's easy to monitor. We could modify each Retrofit call the following way: In the failure callback, check for the error code, if it equals Unauthorized, refresh the OAuth token, then repeat the Retrofit call. However, for this, all calls should be modified, which is not an easily maintainable, and good solution. Is there a way to do this without modifying all Retrofit calls?

Daniel Zolnai
  • 14,536
  • 7
  • 52
  • 64
  • 1
    This looks relevant to my [other question](http://stackoverflow.com/questions/22014958/handling-specific-errors-by-sending-another-request-transparently-in-retrofit). I will look into it again soon, but one possible approach is wrapping OkHttpClient. Something like this: https://github.com/pakerfeldt/signpost-retrofit Also, since I'm using RoboSpice with Retrofit, creating a base Request class may be another possible approach as well. Probably you'll have to figure out how to achieve your flow without a Context though, maybe using Otto/EventBus. – Hassan Ibraheem Mar 21 '14 at 20:39
  • I find it also strange, that Retrofit has been around for quite a long time, and OAuth2 is also a commonly used authentication method, and there are no resources on this. Signpost-Retrofit looks promising, have you tried it yet? – Daniel Zolnai Mar 22 '14 at 09:39
  • No, I think it does more than what I need in this case, as I'm not interested in signing requests. However wrapping OkHttpClient like Signpost-Retrofit does seems like a viable solution, or at least a good starting point. – Hassan Ibraheem Mar 22 '14 at 10:13
  • 1
    Well you could fork it, and remove the unneeded cases. I will look into this maybe today, and post here if I achieved something that might solve our problem. – Daniel Zolnai Mar 22 '14 at 11:12
  • I looked a bit deeper into it, and it turns out that signpost can't handle token refreshes. However, I found an API wrapper for GetGlue, which uses retrofit, and handles token refreshes (this also uses a custom OkHttpClient): https://github.com/UweTrottmann/getglue-java – Daniel Zolnai Mar 22 '14 at 11:40
  • 2
    Turned out, that the library didn't handle refreshing tokens, but gave me an idea. I made a small gist about some !untested code, but in theory, I think it should work: https://gist.github.com/ZolnaiDani/9710849 – Daniel Zolnai Mar 22 '14 at 17:20
  • That seems to be in the right direction, the tricky part would be announcing the new tokens and persisting them. That's where you should either obtain a Context from somewhere, or announce it with Otto/EventBus. – Hassan Ibraheem Mar 23 '14 at 07:45
  • I updated my gist. I will set up a local OAuth server, and test things out, I don't guarantee the code working yet. – Daniel Zolnai Mar 23 '14 at 11:20
  • @DanielZolnai, I like your gist, but what will happen if two parallel requests fails on expired token and both ask new one? – neworld Jun 09 '14 at 10:00
  • 3
    @neworld A solution I can think of: make the changeTokenInRequest(...) synchronized, and at the first line, check when was the last time the token was refreshed. If it has been just some seconds (milliseconds) ago, do not refresh the token. You can also set this timeframe to 1 hour or so, to stop constantly requesting new tokens when there's another problem outside the token being outdated. – Daniel Zolnai Jun 09 '14 at 11:54
  • The gist got moved to: https://gist.github.com/dzolnai/9710849 – Daniel Zolnai Sep 26 '14 at 10:05
  • A way to do this besides extending `OkHttp` would be so awesome. – theblang Jan 12 '15 at 20:08
  • 2
    Retrofit 1.9.0 just added support for OkHttp 2.2, which has interceptors. This should make your job a lot easier. For more info, see: https://github.com/square/retrofit/blob/master/CHANGELOG.md#version-190-2015-01-07 and https://github.com/square/okhttp/wiki/Interceptors You have to extend OkHttp for these too, though. – Daniel Zolnai Jan 12 '15 at 20:13
  • Definitely use the new [Interceptor](https://github.com/square/okhttp/wiki/Interceptors) API to do something like this now if you are using `OkHttp`. @DanielZolnai, you actually don't have to extend `OkHttp` to do this with an `Interceptor`. See [my answer below](http://stackoverflow.com/a/28285627/1747491). – theblang Feb 02 '15 at 19:54

9 Answers9

232

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);
Community
  • 1
  • 1
lgvalle
  • 3,247
  • 1
  • 16
  • 13
  • 2
    https://github.com/square/okhttp/wiki/Recipes#handling-authentication Here is a good solution, it works for me. But I don't have response.priorResponse(). Any clues? – DàChún Aug 20 '15 at 12:46
  • yes, you are right, I changed the Response class, then it works. thanks Igvalle – DàChún Aug 20 '15 at 21:11
  • 6
    Does this mean that every request will fail always 1 time or do you add the token when doing requests? – Jdruwe Sep 30 '15 at 17:53
  • 12
    @Jdruwe it looks like this code would fail 1 time, and then it will make the request. however if you add an interceptor thats only purpose is to always add an access token (regardless if its expired or not) then this will only be called when a 401 is received which will only occur when that token has expired. – narciero Oct 03 '15 at 16:59
  • 66
    `TokenAuthenticator` depends an a `service` class. The `service` class depends on an `OkHttpClient` instance. To create an `OkHttpClient` I need the `TokenAuthenticator`. How can I broke this cycle? Two different `OkHttpClient`s? They are going to have different connection pools... – Brais Gabin Mar 09 '16 at 13:46
  • The `Authenticator` is also executed if the response code is **407**. See [the docs](http://square.github.io/okhttp/3.x/okhttp/). – Albert Vila Calvo Mar 15 '16 at 15:46
  • @Igvalle How can we implement this in Retrofit 2.0? – Rajesh Khadka May 31 '16 at 09:23
  • 1
    @RajeshKhadka seems it's been already asked here: http://stackoverflow.com/questions/35238894/android-retrofit-2-authenticator-result – lgvalle Jun 01 '16 at 20:13
  • 3
    How is that a good idea? Every single request you make will be made twice. Enter the room. If your head hits the door, use the key. – Paul Woitaschek Sep 21 '16 at 09:36
  • @PaulWoitaschek not every single, only when you get a `401` – lgvalle Sep 22 '16 at 14:17
  • 2
    @lgvalle Well if your communicating with your backend you mostly need every single request to have the auth header. So you'll get a 401 for every single request a directly. Correct me if I'm wrong! (I'd love to be wrong ;-)) – Paul Woitaschek Sep 22 '16 at 16:09
  • 1
    @PaulWoitaschek that's fine, maybe I am wrong :) The point here is the first call will get a 401, the `Authenticator` will refresh the token and then, the rest will be ok (until token expires again) – lgvalle Sep 22 '16 at 16:37
  • 8
    How about many parallel requests which need to refresh token? It will be many refresh token request at same time. How to avoid it? – Igor Kostenko Nov 02 '16 at 09:23
  • 3
    @Ihor Kostenko brought valid point in terms of current Authenticator behaviour. When we are triggering multiple, parallel network requests and at that time access_token will be invalidated, that will result in many refresh token flows... in most cases that will lead to throwing out user from logged state. First refresh will be fine, but the next one will use most probably old refresh_token which shouldn't be valid anymore. I am trying to to find a solution for that now :) – Dariusz Wiechecki Feb 28 '17 at 10:49
  • 4
    @PaulWoitaschek Paul, do not forget that Authenticator is not the only part of that authentication. You need to have two parts: interceptor which inject proper access_token and then Authenticator in case you don't have valid access_token :) So you will hit Authenticator only when any network requests will return 401 (or 407). You should then get new access token by running refresh flow, and any further requests should be fine, as long as Interceptor with token injection will use proper/updated access_token ;) – Dariusz Wiechecki Feb 28 '17 at 10:52
  • 2
    Ok, so solution for @Ihor's problem could be synchronizing the code inside Authenticator. It solved problem in my case: – Dariusz Wiechecki Mar 01 '17 at 09:09
  • 13
    Ok, so solution for @Ihor's problem could be synchronizing the code inside Authenticator. It solved problem in my case. in Request authenticate(...) method: - do any initalization stuff - start synchronized block ( synchronized(MyAuthenticator.class) { ... } ) - in that block retrieve current access & refresh token - check if failed request was using latest access token (resp.request().header("Authorization")) - if not just run it once again with updated access token - otherwise run refresh token flow - update/persist updated access and refresh token - finish synchronized block - rerun – Dariusz Wiechecki Mar 01 '17 at 09:16
  • @nomrasco can you provide code? i'm have same troubles and get stunned for now. – Eugene Stepanov Mar 01 '17 at 22:34
  • 2
    I wanna know more about your service.refreshToken() method. How do you handle it synchronously without crashing the application? – Mehdi Pourfar Mar 29 '17 at 12:31
  • 2
    @nomrasco could you provide code on how to synchronize the authenticator? If I have three requests fire off with an invalid token... my authenticator refreshes 3 times. – Codeversed Mar 29 '17 at 21:23
  • 1
    @Codeversed I am sorry, I cannot share that code as it is closed/proprietary source code :( I will try to prepare small example Authenticator following my logic presented above, but I cannot give any guarantee when I will have time for that. – Dariusz Wiechecki Mar 30 '17 at 21:45
  • 1
    Maybe it will save someone else a few minutes, but in the new versions of OkHttp the syntax for adding an authenticator is `OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder(); okHttpClientBuilder.authenticator(authAuthenticator); OkHttpClient okHttpClient = okHttpClientBuilder.build();` – Bjørn Stenfeldt Aug 23 '17 at 19:38
  • @MehdiPourfar You can create synchronously call just almost the same as you are doing other calls with retrofit, but instead using `call.enqueue(callback)` you should use `call.execute()`. – Wojtek Nov 13 '17 at 19:13
  • I have up-voted because this addresses the OP's problem well: The `Authenticator` provides a central hook to capture `401`s. However, I disagree with the statement *"Please do not use Interceptors to deal with authentication"*. Going by Square's own Wiki and Javadoc that the answer links to, `Authenticator`'s intended use case seems to be for challenge-based auth (such as Basic Auth, etc.) where you expect to repeatedly bump into 401 first, prior to building your authorization header based on schemes/realms that might have been conveyed with that 401. [TBC] – Trevor Jan 03 '18 at 17:19
  • 1
    However, the operation of the `Authenticator` doesn't seem to lend itself to the situation where you have a consistent, predetermined authorization token that has a lifetime spanning many requests (e.g. OAuth token, or any token with any kind of TTL). I've therefore done it in an Interceptor, because if it were all done in the `Authenticator`, there would be pointless 401s responded first to each request. Therefore, my approach (which has been mentioned above also) is for `Interceptor` to perform token injection, and the `Authenticator`'s only role is to detect token expiry or revocation. – Trevor Jan 03 '18 at 17:19
  • 1
    @Trevor, the only thing remains is how to re-authenticate (refresh OAuth token) using _the same okhttp client_? The way how okhttp clients are built (see snippet by @Bjørn), an authenticator doesn't hold client's self reference. Therefore, you need to create a secondary okhttp client inside an authenticator, while in fact, you could use the origin one. – dizcza Jan 12 '18 at 19:20
  • 1
    @dizcza I avoid trying to nest a new HTTP call within (which I feel is overly simplistic anyhow); rather, I flag the non-authenticated condition to an upper 'layer', which decides for itself how to resolve the authentication issue, as well as restarting, resuming, whatever overall work it was trying to do originally. [TBC] – Trevor Jan 13 '18 at 10:40
  • So, in my case, I use `SyncAdapter` + `AccountManager`. So, my `Authenticator` is very simple: If its callback is called, it notifies `AccountManager` to invalidate token it holds. This leads to`AccountManager` asking my `Authenticator` to get a new token... which it does using the existing Retrofit/OKHttp instance. Perhaps you could achieve similar using a global / Singleton instance to hold the client if you're not using `Account`s. Essentially, how the `Authenticator` ultimately leads to the same OkHttp instance being used to refresh the token is just a programming architecture challenge. – Trevor Jan 13 '18 at 10:43
  • i have the same issue with syncing i am hitting multiple API i using zip call of retrofit in that case i have infinite loop of refresh token call i want to refresh token and after refresh all my API to be called synchronously – Nouman Shah Mar 21 '19 at 08:30
  • how to continue old request on getting new refresh token – Rishav Singla Jul 19 '19 at 03:17
  • 1
    I have another case. After 1 day refresh token also expire then how we can know that? – Shweta Chauhan Jan 13 '20 at 07:22
  • Can someone please explain why using the `Authenticator` API is preferred over using `Interceptor`? What scenarios is this "reactive" approach, where we refresh token after a 401 response, preferred over a "proactive" approach, where we refresh the token before making the api call? – TROD May 25 '21 at 15:58
68

If you are using Retrofit >= 1.9.0 then you could make use of OkHttp's new Interceptor, which was introduced in OkHttp 2.2.0. You would want to use an Application Interceptor, which permits you to retry and make multiple calls.

Your Interceptor could look something like this pseudocode:

public class CustomInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        // try the request
        Response response = chain.proceed(request);

        if (response shows expired token) {

            // get a new token (I use a synchronous Retrofit call)

            // create a new request and modify it accordingly using the new token
            Request newRequest = request.newBuilder()...build();

            // retry the request
            return chain.proceed(newRequest);
        }

        // otherwise just pass the original response on
        return response;
    }

}

After you define your Interceptor, create an OkHttpClient and add the interceptor as an Application Interceptor.

    OkHttpClient okHttpClient = new OkHttpClient();
    okHttpClient.interceptors().add(new CustomInterceptor());

And finally, use this OkHttpClient when creating your RestAdapter.

    RestService restService = new RestAdapter().Builder
            ...
            .setClient(new OkClient(okHttpClient))
            .create(RestService.class);

Warning: As Jesse Wilson (from Square) mentions here, this is a dangerous amount of power.

With that being said, I definitely think this is the best way to handle something like this now. If you have any questions please don't hesitate to ask in a comment.

snafu109
  • 572
  • 3
  • 13
theblang
  • 9,669
  • 9
  • 61
  • 116
  • 2
    How are you achieving a synchronous call in Android when Android does not allow network calls on the main thread? I am running into problems returning a Response from an asynchronous call. – lgdroid57 Jun 19 '15 at 21:17
  • 1
    @lgdroid57 You are correct, thus you should already be on another thread when you started the original request that triggered the interceptor to run. – theblang Jun 19 '15 at 21:28
  • 3
    This worked great except I had to make sure to close the previous response or I would leak the previous connection ... final Request newRequest = request.newBuilder()....build(); response.body().close(); return chain.proceed(newRequest); – DallinDyer Jul 08 '15 at 18:27
  • Thanks! I was running into an issue where the Callback of the original request was receiving an error message of "closed" instead of the original response, due to the body being consumed in the Interceptor. I was able to fix this for successful responses, but I was not able to fix this for failed responses. Any suggestions? – lgdroid57 Jul 10 '15 at 03:22
  • Thanks @mattblang, it looks nice. One question: is the request callback guaranteed to be called even on the retry? – Luca Fagioli Sep 02 '15 at 15:20
  • I need ask a question . How is working . If you make a request , firstly interceptor makes a request and if its code not equal to unouthorized(mean 401) then go normal request. If equal to 401 then make refresh token and do normal request. If its doin like i think all request count doubled. Am i wrang here ? – Yasin Kaçmaz Feb 01 '16 at 15:18
25

TokenAuthenticator depends an a service class. The service class depends on an OkHttpClient instance. To create an OkHttpClient I need the TokenAuthenticator. How can I break this cycle? Two different OkHttpClients? They are going to have different connection pools..

If you have, say, a Retrofit TokenService that you need inside your Authenticator but you would only like to set up one OkHttpClient you can use a TokenServiceHolder as a dependency for TokenAuthenticator. You would have to maintain a reference to it at the application (singleton) level. This is easy if you are using Dagger 2, otherwise just create class field inside your Application.

In TokenAuthenticator.java

public class TokenAuthenticator implements Authenticator {

    private final TokenServiceHolder tokenServiceHolder;

    public TokenAuthenticator(TokenServiceHolder tokenServiceHolder) {
        this.tokenServiceHolder = tokenServiceHolder;
    }

    @Override
    public Request authenticate(Proxy proxy, Response response) throws IOException {

        //is there a TokenService?
        TokenService service = tokenServiceHolder.get();
        if (service == null) {
            //there is no way to answer the challenge
            //so return null according to Retrofit's convention
            return null;
        }

        // Refresh your access_token using a synchronous api request
        newAccessToken = service.refreshToken().execute();

        // 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;
    }

In TokenServiceHolder.java:

public class TokenServiceHolder {

    TokenService tokenService = null;

    @Nullable
    public TokenService get() {
        return tokenService;
    }

    public void set(TokenService tokenService) {
        this.tokenService = tokenService;
    }
}

Client setup:

//obtain instance of TokenServiceHolder from application or singleton-scoped component, then
TokenAuthenticator authenticator = new TokenAuthenticator(tokenServiceHolder);
OkHttpClient okHttpClient = new OkHttpClient();    
okHttpClient.setAuthenticator(tokenAuthenticator);

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .client(okHttpClient)
    .build();

TokenService tokenService = retrofit.create(TokenService.class);
tokenServiceHolder.set(tokenService);

If you are using Dagger 2 or a similar dependency injection framework there are some examples in the answers to this question

David Rawson
  • 17,631
  • 6
  • 78
  • 112
  • Where is `TokenService` class created? – Yogesh Suthar Nov 08 '17 at 16:30
  • @YogeshSuthar it's a Retrofit service - see [the related question](https://stackoverflow.com/a/43929302/5241933) – David Rawson Nov 08 '17 at 18:49
  • Thanks, can you provide the implementation of `refreshToken()` from `service.refreshToken().execute();`. Not able to find it's implementation anywhere. – Yogesh Suthar Nov 11 '17 at 08:13
  • @Yogesh The refreshToken method is from your API. Whatever you call to refresh a token (a call with username and password maybe?). Or maybe a request where you submit a token and the response is a new token – David Rawson Nov 11 '17 at 08:21
8

Using TokenAuthenticator like @theblang answer is a correct way for handle refresh_token.

Here is my implement (I have using Kotlin, Dagger, RX but you may use this idea for implement to your case)
TokenAuthenticator

class TokenAuthenticator @Inject constructor(private val noneAuthAPI: PotoNoneAuthApi, private val accessTokenWrapper: AccessTokenWrapper) : Authenticator {

    override fun authenticate(route: Route, response: Response): Request? {
        val newAccessToken = noneAuthAPI.refreshToken(accessTokenWrapper.getAccessToken()!!.refreshToken).blockingGet()
        accessTokenWrapper.saveAccessToken(newAccessToken) // save new access_token for next called
        return response.request().newBuilder()
                .header("Authorization", newAccessToken.token) // just only need to override "Authorization" header, don't need to override all header since this new request is create base on old request
                .build()
    }
}

For prevent dependency cycle like @Brais Gabin comment, I create 2 interface like

interface PotoNoneAuthApi { // NONE authentication API
    @POST("/login")
    fun login(@Body request: LoginRequest): Single<AccessToken>

    @POST("refresh_token")
    @FormUrlEncoded
    fun refreshToken(@Field("refresh_token") refreshToken: String): Single<AccessToken>
}

and

interface PotoAuthApi { // Authentication API
    @GET("api/images")
    fun getImage(): Single<GetImageResponse>
}

AccessTokenWrapper class

class AccessTokenWrapper constructor(private val sharedPrefApi: SharedPrefApi) {
    private var accessToken: AccessToken? = null

    // get accessToken from cache or from SharePreference
    fun getAccessToken(): AccessToken? {
        if (accessToken == null) {
            accessToken = sharedPrefApi.getObject(SharedPrefApi.ACCESS_TOKEN, AccessToken::class.java)
        }
        return accessToken
    }

    // save accessToken to SharePreference
    fun saveAccessToken(accessToken: AccessToken) {
        this.accessToken = accessToken
        sharedPrefApi.putObject(SharedPrefApi.ACCESS_TOKEN, accessToken)
    }
}

AccessToken class

data class AccessToken(
        @Expose
        var token: String,

        @Expose
        var refreshToken: String)

My Interceptor

class AuthInterceptor @Inject constructor(private val accessTokenWrapper: AccessTokenWrapper): Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()
        val authorisedRequestBuilder = originalRequest.newBuilder()
                .addHeader("Authorization", accessTokenWrapper.getAccessToken()!!.token)
                .header("Accept", "application/json")
        return chain.proceed(authorisedRequestBuilder.build())
    }
}

Finally, add Interceptor and Authenticator to your OKHttpClient when create service PotoAuthApi

Demo

https://github.com/PhanVanLinh/AndroidMVPKotlin

Note

Authenticator flow
  • Example API getImage() return 401 error code
  • authenticate method inside TokenAuthenticator will fired
  • Synchronize noneAuthAPI.refreshToken(...) called
  • After noneAuthAPI.refreshToken(...) response -> new token will add to header
  • getImage() will AUTO called with new header (HttpLogging WILL NOT log this call) (intercept inside AuthInterceptor WILL NOT CALLED)
  • If getImage() still failed with error 401, authenticate method inside TokenAuthenticator will fired AGAIN and AGAIN then it will throw error about call method many time(java.net.ProtocolException: Too many follow-up requests). You can prevent it by count response. Example, if you return null in authenticate after 3 times retry, getImage() will finish and return response 401

  • If getImage() response success => we will result the result normally (like you call getImage() with no error)

Hope it help

Linh
  • 43,513
  • 18
  • 206
  • 227
  • This solution uses 2 different OkHttpClients, as evident in your ServiceGenerator class. – SpecialSnowflake Sep 03 '18 at 08:31
  • @SpecialSnowflake you are right. If you follow my solution, you need to create 2 OkHttp because I created 2 service (oauth and none auth). I think it will not cause any problem. Let me know your idea – Linh Sep 03 '18 at 09:11
1

I know this an old thread, but just in case someone stumbled in it.

TokenAuthenticator depends an a service class. The service class depends on an OkHttpClient instance. To create an OkHttpClient I need the TokenAuthenticator. How can I break this cycle? Two different OkHttpClients? They are going to have different connection pools..

I was facing the same problem, but I wanted to create only one OkHttpClient becuase I don't think that I need another one for just the TokenAuthenticator itself, I was using Dagger2, so I ended up providing the service class as Lazy injected in the TokenAuthenticator, you can read more about Lazy injection in dagger 2 here, but it's like basically saying to Dagger to NOT go and create the service needed by the TokenAuthenticator right away.

You can refer to this SO thread for sample code: How to resolve a circular dependency while still using Dagger2?

Boda
  • 239
  • 2
  • 11
1

Using one Interceptor (inject the token) and one Authenticator (refresh operations) do the job but:

I had a double call problem too: the first call always returned a 401: the token wasn't inject at the first call (interceptor) and the authenticator was called: two requests were made.

The fix was just to reaffect the request to the build in the Interceptor:

BEFORE:

private Interceptor getInterceptor() {
    return (chain) -> {
        Request request = chain.request();
        //...
        request.newBuilder()
                .header(AUTHORIZATION, token))
                .build();
        return chain.proceed(request);
    };
}

AFTER:

private Interceptor getInterceptor() {
    return (chain) -> {
        Request request = chain.request();
        //...
        request = request.newBuilder()
                .header(AUTHORIZATION, token))
                .build();
        return chain.proceed(request);
    };
}

IN ONE BLOCK:

private Interceptor getInterceptor() {
    return (chain) -> {
        Request request = chain.request().newBuilder()
                .header(AUTHORIZATION, token))
                .build();
        return chain.proceed(request);
    };
}

Hope it helps.

Edit: I didn't find a way to avoid the first call to always returning 401 using only the authenticator and no interceptor

Sigrun
  • 48
  • 4
0

You can try creating a base class for all your loaders in which you would be able to catch a particular exception and then act as you need. Make all your different loaders extend from the base class in order to spread the behaviour.

k3v1n4ud3
  • 2,644
  • 1
  • 13
  • 19
  • Retrofit does not work that way. It uses java annotations, and interfaces to describe an API call – Daniel Zolnai Mar 17 '14 at 10:18
  • I know how retrofit works, but you're still "wrapping" your API calls inside an AsynTask aren't you? – k3v1n4ud3 Mar 17 '14 at 10:26
  • No, I use the calls with a Callback, so they run asynchronously. – Daniel Zolnai Mar 17 '14 at 10:32
  • Then you can probably create a base callback class and make all your callbacks extend it. – k3v1n4ud3 Mar 17 '14 at 11:25
  • Yes, I thought about that too, but the main problem is, that I need to recall my method with the same parameters again. But how could I provide that method to the customized callback class? – Daniel Zolnai Mar 17 '14 at 11:34
  • In the callback class of retrofit, you get a RetrofitError in case of failure which contains the URL you requested so you can parse it and extract the parameters you previously passed. It seems a bit overkill though, reparsing something you already had before... I saw a page on github mentioning a new feature: https://github.com/square/retrofit/issues/297 Hopefully this will solve your problem in the future. I still think that a clean way would be use AsyncTaskLoader. – k3v1n4ud3 Mar 18 '14 at 02:24
  • Well, I guess I will modify all calls then. The parsing is really too risky, you might never know where that goes wrong. The 2.0 draft is 7 months old, and they don't really have a progress on that, so I can't rely on that. – Daniel Zolnai Mar 18 '14 at 07:46
  • 2
    Any solution to this? Is exactly my case here. =/ – Hugo Nogueira Sep 18 '14 at 02:35
  • @hugomn If you are using `OkHttp` with Retrofit then you can make use the new [Interceptor](https://github.com/square/okhttp/wiki/Interceptors) API to do something like this. – theblang Feb 02 '15 at 19:59
0

After Long research, I customized Apache client to handle Refreshing AccessToken For Retrofit In which you send access token as parameter.

Initiate your Adapter with cookie Persistent Client

restAdapter = new RestAdapter.Builder()
                .setEndpoint(SERVER_END_POINT)
                .setClient(new CookiePersistingClient())
                .setLogLevel(RestAdapter.LogLevel.FULL).build();

Cookie Persistent client which maintains cookies for all requests and checks with each request response, if it is unauthorized access ERROR_CODE = 401, refresh access token and recall the request, else just processes request.

private static class CookiePersistingClient extends ApacheClient {

    private static final int HTTPS_PORT = 443;
    private static final int SOCKET_TIMEOUT = 300000;
    private static final int CONNECTION_TIMEOUT = 300000;

    public CookiePersistingClient() {
        super(createDefaultClient());
    }

    private static HttpClient createDefaultClient() {
        // Registering https clients.
        SSLSocketFactory sf = null;
        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore
                    .getDefaultType());
            trustStore.load(null, null);

            sf = new MySSLSocketFactory(trustStore);
            sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        HttpParams params = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(params,
                CONNECTION_TIMEOUT);
        HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("https", sf, HTTPS_PORT));
        // More customization (https / timeouts etc) can go here...

        ClientConnectionManager cm = new ThreadSafeClientConnManager(
                params, registry);
        DefaultHttpClient client = new DefaultHttpClient(cm, params);

        // Set the default cookie store
        client.setCookieStore(COOKIE_STORE);

        return client;
    }

    @Override
    protected HttpResponse execute(final HttpClient client,
            final HttpUriRequest request) throws IOException {
        // Set the http context's cookie storage
        BasicHttpContext mHttpContext = new BasicHttpContext();
        mHttpContext.setAttribute(ClientContext.COOKIE_STORE, COOKIE_STORE);
        return client.execute(request, mHttpContext);
    }

    @Override
    public Response execute(final Request request) throws IOException {
        Response response = super.execute(request);
        if (response.getStatus() == 401) {

            // Retrofit Callback to handle AccessToken
            Callback<AccessTockenResponse> accessTokenCallback = new Callback<AccessTockenResponse>() {

                @SuppressWarnings("deprecation")
                @Override
                public void success(
                        AccessTockenResponse loginEntityResponse,
                        Response response) {
                    try {
                        String accessToken =  loginEntityResponse
                                .getAccessToken();
                        TypedOutput body = request.getBody();
                        ByteArrayOutputStream byte1 = new ByteArrayOutputStream();
                        body.writeTo(byte1);
                        String s = byte1.toString();
                        FormUrlEncodedTypedOutput output = new FormUrlEncodedTypedOutput();
                        String[] pairs = s.split("&");
                        for (String pair : pairs) {
                            int idx = pair.indexOf("=");
                            if (URLDecoder.decode(pair.substring(0, idx))
                                    .equals("access_token")) {
                                output.addField("access_token",
                                        accessToken);
                            } else {
                                output.addField(URLDecoder.decode(
                                        pair.substring(0, idx), "UTF-8"),
                                        URLDecoder.decode(
                                                pair.substring(idx + 1),
                                                "UTF-8"));
                            }
                        }
                        execute(new Request(request.getMethod(),
                                request.getUrl(), request.getHeaders(),
                                output));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }

                @Override
                public void failure(RetrofitError error) {
                    // Handle Error while refreshing access_token
                }
            };
            // Call Your retrofit method to refresh ACCESS_TOKEN
            refreshAccessToken(GRANT_REFRESH,CLIENT_ID, CLIENT_SECRET_KEY,accessToken, accessTokenCallback);
        }

        return response;
    }
}
Suneel Prakash
  • 606
  • 5
  • 7
  • Is there a reason that you are using the ApacheClient instead of the suggested solution? Not that it isn't a good solution, but it needs a lot more coding, compared to using Interceptors. – Daniel Zolnai Apr 14 '15 at 18:15
  • Its customized to be cookie persistent client, maintains session throughout services. Even in Request Intercceptor, you can add accesstoken in headers. But what if you want to add it as a param? Also OKHTTPClient is having limitations. ref: http://stackoverflow.com/questions/24594823/okhttp-noclassdeffounderror – Suneel Prakash Apr 15 '15 at 18:03
  • It is more generalised to be used in any case 1. Cookie Persistent Client 2. Accepts HTTP and HTTPS requests 3. Update Access Token in Params. – Suneel Prakash Apr 15 '15 at 18:04
-1

To anyone who wanted to solve concurrent/parallel calls when refreshing token. Here's a workaround

class TokenAuthenticator: Authenticator {

    override fun authenticate(route: Route?, response: Response?): Request? {
        response?.let {
            if (response.code() == 401) {
                while (true) {
                    if (!isRefreshing) {
                        val requestToken = response.request().header(AuthorisationInterceptor.AUTHORISATION)
                        val currentToken = OkHttpUtil.headerBuilder(UserService.instance.token)

                        currentToken?.let {
                            if (requestToken != currentToken) {
                                return generateRequest(response, currentToken)
                            }
                        }

                        val token = refreshToken()
                        token?.let {
                            return generateRequest(response, token)
                        }
                    }
                }
            }
        }

        return null
    }

    private fun generateRequest(response: Response, token: String): Request? {
        return response.request().newBuilder()
                .header(AuthorisationInterceptor.USER_AGENT, OkHttpUtil.UA)
                .header(AuthorisationInterceptor.AUTHORISATION, token)
                .build()
    }

    private fun refreshToken(): String? {
        synchronized(TokenAuthenticator::class.java) {
            UserService.instance.token?.let {
                isRefreshing = true

                val call = ApiHelper.refreshToken()
                val token = call.execute().body()
                UserService.instance.setToken(token, false)

                isRefreshing = false

                return OkHttpUtil.headerBuilder(token)
            }
        }

        return null
    }

    companion object {
        var isRefreshing = false
    }
}