You might run into a scenario where you might require conditional authentication with Retrofit 2.0.
This post provides an example of integration with the Lyft API. In case of the Lyft API, first we need to authenticate with and query the oauth/token endpoint to obtain the OAUTH token, and then use thisaccessToken in other service calls. Also, such access tokens have an expiry time(1 hour), so ideally there should be a mechanism to handle this scenario.
One lazy (tends out to be perfect) solution is to use interceptors and compare the HTTP Response codefrom the service to see whether the code is 401. If the code is 401, you can assume that the token has either expired or was never obtained initially, either way you would need to re-authenticate and query the endpoint to obtain the accessToken.
The code block below shows how this is done. To access the entire source code you can visit Lyft-Client on Github.
/** * This method initializes the retrofit clients * a) One for the initial authentication end point * b) Other for other service requests */ private void initializeRetrofitClients() { OkHttpClient.Builder builder = new OkHttpClient().newBuilder(); OkHttpClient clientNormal; OkHttpClient clientAuthenticated; builder.interceptors().add(new Interceptor() { @Override public okhttp3.Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Request.Builder builder = originalRequest.newBuilder().header("Authorization:Bearer ", accessToken). method(originalRequest.method(), originalRequest.body()); okhttp3.Response response = chain.proceed(builder.build()); /** implies that the token has expired or was never initialized */ if (response.code() == 401) { tokenExpired = true; logger.info("Token Expired"); getAuthenticationToken(); builder = originalRequest.newBuilder().header("Authorization:Bearer ", accessToken). method(originalRequest.method(), originalRequest.body()); response = chain.proceed(builder.build()); } return response; } }); clientAuthenticated = builder.build(); retrofitAuthenticated = new Retrofit.Builder().client(clientAuthenticated) .baseUrl(API_ENDPOINT) .addConverterFactory(GsonConverterFactory.create()) .build(); OkHttpClient.Builder builder1 = new OkHttpClient().newBuilder(); builder1.authenticator(new Authenticator() { @Override public Request authenticate(Route route, okhttp3.Response response) throws IOException { String authentication = Credentials.basic(CLIENT_ID, CLIENT_SECRET); Request.Builder builder = response.request().newBuilder().addHeader("Authorization", authentication); return builder.build(); } }); clientNormal = builder1.build(); retrofit = new Retrofit.Builder().client(clientNormal). baseUrl(API_ENDPOINT). addConverterFactory(GsonConverterFactory.create()).build(); } /** * Is invoked only when the access token is required * Or it expires */ private void getAuthenticationToken() { LyftService lyftService = this.retrofit.create(LyftService.class); Call<OAuthResponse> authRequestCall = lyftService.getAccessToken(oAuthRequest); Response<OAuthResponse> response = null; try { response = authRequestCall.execute(); if (response.isSuccessful()) { accessToken = response.body().getAccessToken(); } } catch (IOException e) { logger.error("Exception occurred due to ", e); } }
As can be seen in the above code example, we build two OkHttpClient objects, the clientNormal object is configured to use HTTP basic authentication and is used by retrofit object to query the getAccessTokenendpoint to obtain the access token, this accessToken is required by other Lyft service endpoints. TheclientAuthenticated object uses a interceptor to set the Authorization:Bearer header with the value of theaccessToken, which is required for all other service endpoints.
In the method initializeRetrofitClients it can be seen that initially, we just invoke the service endpoint by using the value of accessToken (by calling chain.proceed) and if we see that the response code is 401, we invoke getAuthenticationToken followed by another call to chain.proceed with the new value of accessToken. For subsequent calls, the interceptor will use the stored value of accessToken. This lazy invocation to obtain the access token is better because this way the logic for deciding when to obtain accessToken is not hardcoded. In addition, this keeps the code simple by avoiding unncessary checks.
Hope this post was helpful in clearing the use of interceptors for conditional authentication.
You can use the following link to view the entire code on Github repository.
Lyft-Client on Github.