I am using Auth0, which gives me a JWT (json web token) and a refreshtoken. I use this JWT in the http headers to communicate with my backend.
It could happen, that
I solved this issue by writing an Interceptor
for OkHttp
. It checks the statuscode of the network call. If it's a 403, call Auth0 servers and request a new id_token. Then use this token in a new version of the original request.
To test, I wrote a little webserver that checks the TestHeader for fail or succeed and returns a 403 if it's fail.
public class AuthenticationInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request authenticationRequest = originalRequest.newBuilder()
.header("TestHeader", "fail")
.build();
Response origResponse = chain.proceed(authenticationRequest);
// server should give us a 403, since the header contains 'fail'
if (origResponse.code() == 403) {
String refreshToken = "abcd"; // you got this from Auth0 when logging in
// start a new synchronous network call to Auth0
String newIdToken = fetchNewIdTokenFromAuth0(refreshToken);
// make a new request with the new id token
Request newAuthenticationRequest = originalRequest.newBuilder()
.header("TestHeader", "succeed")
.build();
// try again
Response newResponse = chain.proceed(newAuthenticationRequest);
// hopefully we now have a status of 200
return newResponse;
} else {
return origResponse;
}
}
}
Then I attach this Interceptor to an OkHttpClient which I plug into the Retrofit adapter:
// add the interceptor to an OkHttpClient
public static OkHttpClient getAuthenticatingHttpClient() {
if (sAuthenticatingHttpClient == null) {
sAuthenticatingHttpClient = new OkHttpClient();
sAuthenticatingHttpClient.interceptors().add(new AuthenticationInterceptor());
}
return sAuthenticatingHttpClient;
}
// use the OkHttpClient in a Retrofit adapter
mTestRestAdapter = new RestAdapter.Builder()
.setClient(new OkClient(Network.getAuthenticatingHttpClient()))
.setEndpoint("http://ip_of_server:port")
.setLogLevel(RestAdapter.LogLevel.FULL)
.build();
// call the Retrofit method on buttonclick
ViewObservable.clicks(testNetworkButton)
.map(new Func1<OnClickEvent, Object>() {
@Override
public Object call(OnClickEvent onClickEvent) {
return mTestRestAdapter.fetchTestResponse();
}
}
)
Instead of refreshing tokens only after receiving a 403 response, you could check the expiration time locally and refresh accordingly by checking the token's exp
claim. For example, this example uses the same approach in Angular. It's not specific to Android, but the idea is the same:
jwtInterceptorProvider.tokenGetter = function(store, jwtHelper, auth) {
var idToken = store.get('token');
var refreshToken = store.get('refreshToken');
if (!idToken || !refreshToken) {
return null;
}
// If token has expired, refresh it and return the new token
if (jwtHelper.isTokenExpired(idToken)) {
return auth.refreshIdToken(refreshToken).then(function(idToken) {
store.set('token', idToken);
return idToken;
});
// If not expired, return the token directly
} else {
return idToken;
}
}