Problem Solved 2: Access Token refresh with Okhttp Authenticator
Problem: Access Token Refresh mechanism — It’s a feature in most of the App that we use. We want to keep the user logged-in to our App until user voluntarily wants to Log out from the App. Do you like to login to your favorite App like Instagram, Twitter etc every time you open it. Not really….isn’t it..
Then how such Apps are handling this scenario. Do they really keep their single access-token/JWT (Json Web Token) active for so long, like Years? Not a good idea. Every access token/JWT has a short period of validity after that it will be expired. This is considered as a good security practice. Access keys will be expired after some time say 1–2 hours (in most of the projects I worked till now).
Next question, how to get the new access token if the existing access-token gets expired, it is not wise to make a user to log in again to generate the new key. It is at this stage we have an extra key with us called Refresh-Token.
Note: The standard practice that everyone follows in their Auth system is OAuth. Please check this for more details on OAuth.
In short, the user will be provided with 2 keys namely Access-token and Refresh-token. Access-token is used to access the resources to which user is authorized to. Refresh-token is used to renew his access-token.
Long story short. The user can get access to his resource using Access-token, after some hours if his Access-token gets expired (401 UnAuthorized), Refresh-token can be used to get the new Access-token and continue the access to the resources (so no login needed)
tl;dr
Android Perspective: If your backend is supporting OAuth, then your Access-token will be JWT (Json Web Token), which backend must send you on successful login to use your resource. Now you can check the validity of this JWT a.k.a Access-token by putting this whole key string here. You will get a lot of details out of it, but the most interesting thing is expiry date. It is after this expiry date, the JWT will not be valid anymore. So if you call your endpoint with this JWT, backend should respond with the 401 UnAuthorized response. This behavior is applicable for all the endpoint where valid JWT is needed to access it.
Now its time use another key which we have Refresh-token. Backend should provide another rest API which accepts Refresh-token and provides a new set of Access-token/JWT and Refresh-token. Now App should continue working with these new set of JWT and Refresh-token just like before.
Now as a App user this whole process looks seamless..
BUT how to handle this in our Android App development perspective. We don’t like to check everytime 401 UnAuthorised is returned for the endpoint. Get the JWT refreshed using refresh-token and remember the endpoint which got failed and call it again with the new JWT.
Doing this manually for each endpoint is really painful and I think it’s difficult to manage the code if this pattern has to be followed everywhere.
It’s at this time Okhttp networking library for Android will come for the support. If you are using Retrofit 2.0 with Okhttp this step can be handled in a more elegant way.
Okhttp has a concept of Interceptor which helps to monitor, rewrite, and retry calls. I have performed the operations like reading cookie values with the help of Interceptors. But in our problem, we want to read the response and perform retry based on the response. Before Okhttp 2+ we performed this whole task using this Interceptor. Now in Okhttp 2.0, we have Authenticaor described here
public class TokenAuthenticator implements Authenticator {
@Override
public Request authenticate(Route route, Response response) throws IOException {
if (response.code() == 401) {
Call<Void> refreshCall = refereshAccessToken(refereshToken);
//make it as retrofit synchronous call
Response<Void> refreshResponse = refreshCall.execute();
if (refreshResponse != null && refreshResponse.code() == 200) {
//read new JWT value from response body or interceptor depending upon your JWT availability logic
newCookieValue = readNewJwtValue();
return response.request().newBuilder()
.header("basic-auth", newCookieValue)
.build();
} else {
return null;
}
}
}
}
The Code looks self-explanatory, check response code if it is 401, call your refresh-token endpoint. It will provide the new JWT. Please make sure that refresh-token endpoint call is Synchronous (blocking type), otherwise, there are good chances that execution continues with a NULL value of response and authenticate will return a NULL value for Request Object which indicates 401 response.
Add this class while creating Okhttp client object
mHttpClient.authenticator(new TokenAuthenticator());
Now whenever 401 appears for any web request, control comes directly to authenticate() callsback. Calls refresh-token endpoint with refresh-token, create a new Request object with the update access-token/JWT and returns it. Thereafter API which got failed because of 401 will be triggered again and it works correctly this time since access-token is updated.
Hope the information is helpful :-)