Yasin Kaçmaz Yasin Kaçmaz - 5 months ago 73
Android Question

Android Retrofit2 Refresh Oauth 2 Token

I am using

Retrofit
and
OkHttp
libraries.
So i have
Authenticator
which authanticate user if gets 401 response.

My
build.gradle
is like that :


compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
compile 'com.squareup.okhttp3:okhttp:3.1.2'


And my custom
Authenticator
is here :


import java.io.IOException;
import okhttp3.Authenticator;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;

public class CustomAuthanticator implements Authenticator {
@Override
public Request authenticate(Route route, Response response) throws IOException {

//refresh access token via refreshtoken

Retrofit client = new Retrofit.Builder()
.baseUrl(baseurl)
.addConverterFactory(GsonConverterFactory.create())
.build();
APIService service = client.create(APIService.class);
Call<RefreshTokenResult> refreshTokenResult=service.refreshUserToken("application/json", "application/json", "refresh_token",client_id,client_secret,refresh_token);
//this is syncronous retrofit request
RefreshTokenResult refreshResult= refreshTokenResult.execute().body();
//check if response equals 400 , mean empty response
if(refreshResult!=null) {
//save new access and refresh token
// than create a new request and modify it accordingly using the new token
return response.request().newBuilder()
.header("Authorization", newaccesstoken)
.build();


}
else
{
//we got empty response and return null
//if we dont return null this method is trying to make so many request
//to get new access token
return null;

}



}}


This is my APIService class

import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.POST;
import retrofit2.http.Query;


public interface APIService {


@FormUrlEncoded
@Headers("Cache-Control: no-cache")
@POST("token")
public Call<RefreshTokenResult> refreshUserToken(@Header("Accept") String accept, @Header("Content-Type") String contentType, @Field("grant_type") String grantType,
@Field("client_id") String clientId, @Field("client_secret") String clientSecret, @Field("refresh_token") String refreshToken);}


I am using authanticator like that

CustomAuthanticator customAuthanticator=new CustomAuthanticator();
OkHttpClient okClient = new OkHttpClient.Builder()
.authenticator(customAuthanticator)
.build();
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.create();
Retrofit client = new Retrofit.Builder()
.baseUrl(getResources().getString(R.string.base_api_url))
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okClient)
.build();

//then make retrofit request


So my question is : Sometimes i get new access token and continue work, requests . But sometimes i get 400 response which means empty response. So my old refresh token invalid and i cant get new token. Normally our refresh token expires in 1 year. So how i can do this. Please help me !

Answer

I will explain how solved this problem.

First of all the refresh token process is critical process. In my application and most of applications doing this : If refresh token fails logout current user and warn user to login .(Maybe you can retry refresh token process by 2-3-4 times by depending you)

I recreated my refresh token using HttpUrlConnection which simplest connection method. No cache , no retries , only try connection and get answer .

So this is my new refresh token process :

My refresh method

public boolean refreshToken(String url,String refresh,String cid,String csecret) throws IOException
{
    URL refreshUrl=new URL(url+"token");
    HttpURLConnection urlConnection = (HttpURLConnection) refreshUrl.openConnection();
    urlConnection.setDoInput(true);
    urlConnection.setRequestMethod("POST");
    urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
    urlConnection.setUseCaches(false);
    String urlParameters  = "grant_type=refresh_token&client_id="+cid+"&client_secret="+csecret+"&refresh_token="+refresh;
    // Send post request
    urlConnection.setDoOutput(true);
    DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
    wr.writeBytes(urlParameters);
    wr.flush();
    wr.close();
    int responseCode = urlConnection.getResponseCode();
    Log.v("refresh http url","Post parameters : " + urlParameters);
    Log.v("refresh http url","Response Code : " + responseCode);
    BufferedReader in = new BufferedReader(
            new InputStreamReader(urlConnection.getInputStream()));
    String inputLine;
    StringBuffer response = new StringBuffer();

    while ((inputLine = in.readLine()) != null) {
        response.append(inputLine);
    }
    in.close();
   // handle response like retrofit, put response to POJO class by using Gson
   // you can find RefreshTokenResult class in my question

    Gson gson = new Gson();
    RefreshTokenResult refreshTokenResult=gson.fromJson(response.toString(),RefreshTokenResult.class);
    if(responseCode==200)
    {
        //handle new token ...
        return true;
    }
        //cannot refresh
        Log.v("refreshtoken", "cannot refresh , response code : "+responseCode);
        return false;
    }

In authenticate method

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

    String userRefreshToken="your refresh token";
    String cid="your client id";
    String csecret="your client secret";
    String baseUrl="your base url"
    refreshResult=refreshToken(baseUrl,userRefreshToken,cid,csecret);
    if(refreshResult)
    {
        //refresh is successful
        String newaccess="your new access";
        return response.request().newBuilder()
                .header("Authorization", newaccess)
                .build();
    }
    else {
        //refresh failed , maybe you can logout user
        return null;
    }
    }