I'm using Retrofit to commuicate to my server.
I have to call the login endpoint first, then I got a cookie (auth. token), which I store automatically, and the following requests are being authorized.
This 'session' expires if there is no request for 1 hour.
How should I do the automatic re-authenticating?
Obviously it's not a good idea, that I ping the login endpont with the username/password before every "real" request, to be sure that my client has not expired yet.
I tried adding interceptor to the okHttpClient, and check whether my "real" response (not the login) gets back code 401. In that case I should call the login endpoint, and after that, I have to repeat the "real" call.
How can I "save" the request, call login, and after that repeat the first request when I'm authenticated again?
private static Retrofit retrofit;
public static Retrofit getClient( final Context context ) {
if ( retrofit == null ) {
ClearableCookieJar cookieJar =
new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context));
final OkHttpClient okHttpClient =
new OkHttpClient.Builder()
.followSslRedirects(false)
.followRedirects(false)
.cookieJar(cookieJar)
.addInterceptor(chain -> {
Request request = chain.request();
Response response = chain.proceed(request);
if ( response.code() == 401 ) {
if ( !response.message().contains("Incorrect") ) {
// THIS IS THE CASE, when I try to call an endpoint with expired token
// I need to call login again, and then repeat this failed request
}
}
return response;
})
.build();
retrofit = new Retrofit.Builder()
.baseUrl(URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
}
return retrofit;
}
Obviously I can check manually every request's response code, and if it's 401, I call login, and when it's done, I call my request again. But I'm hoping that there is an easier (built-in solution with interceptors, so I don't have to implement this logic everywhere)
After login response, save the token, then carry this token to commuicate with server, so that user don't need to login again.
When app gets a response like "401:expired token" during any request, just delete the saved token and all the user data and show a dialog let user jump to LoginActivity. Then repeat.
Related
The Task
I have a situation in which if a refresh token request comes back with a 401, I would force a logout by clearing the user from the room and from memory. I would want to achieve this from inside of the Authenticator OR the Interceptor so I don't have to check for 401 as a response to each call.
The Authenticator
public Request authenticate(Route route, Response response) throws IOException {
// if (responseCount(response) >= 3) {
// return null;
// }
//
UserCredentials userCredentials = new
UserCredentials().
CreateRefreshTokenRequest("refresh_token",
requestHeaders.getAccessToken().getRefreshToken(),
oAuthClient.getClientId(),
oAuthClient.getClientSecret());
Request.Builder builder = response.request().newBuilder();
if (accountsServicesHolder.getAccountsServices() == null)
return null;
//this HAS TO BE a Synchronous process. The usual RXjava shenanigans can't be applied here.
try {
//Synchronous call to get new token
AccessToken accessToken = accountsServicesHolder.
getAccountsServices().
refreshToken(userCredentials.toMap()).
blockingGet();
//persist the token in the db
//userDao.save()
//persist the token in memory
//send an updated version to the backend
builder.header("Authorization", "Bearer " + accessToken.getAccessToken());
} catch (Exception ex) {
if (ex instanceof HttpException) {
return builder.
url(linksProvider.getProductionUrl().concat(logoutEndpoint)).
build(); //Redirecting this authenticator to a logout ws so that we do not get a 401 . Something goes wrong here
}
}
return builder.build();
}
As you can see, if we do not get a token on basic of the refresh token, I am cunningly inserting a new URL into the builder. This is so that I do not get a 401 being bubbled up to where I made the initial call. I do not want a user to get a "401 You have not authorized" message rather I would display a toast saying "Session Expired"
The Request Interceptor
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder builder = original.newBuilder().
header("Authorization", "Bearer " + requestHeaders.getAccessToken().getAccessToken()).
header("Accept-Language", requestHeaders.getLanguage()).
method(original.method(), original.body());
Request newRequest = builder.build();
Response response = chain.proceed(newRequest);
if (response.code() == 200
&& response.request().url().toString().contains("logout")) {
forceUserOut();
}
return response;
}
The forceUserOut method Broadcasts to the system that a logout has happened.
Since we have protected against a 401 now no errors would be displayed to the user.
The Problem
Okay, now we come to the problem. The place where I thought I was being cunned is perhaps not so smart after all. The authenticator runs an extra time after the logout API is called which runs after a thing that is supposed to happen afterward twice.
As far as I understand, the authenticator should have just executed the logout service and the interceptor should have started doing its job then.
The authenticator, however, does the following
1) Tries to get the new access token
2) Fails and calls the logout API (invokes the Interceptor and broadcasts the logout)
3) Fails and calls the logout API (invokes the interceptor and broadcasts the logout)
4) End
I know I might be breaking something inside the OkHttps authentication mechanism, but what other methods could I use in order to achieve a forced logout in a way I do not have to write a 401 check on all the APIs?
Extremely apologetic for the long question.
I'm working with the authenticator of OKHttp that will retry to get new access token if we got 401 status error, but my app have to call many APIs in the same time, resulting in corrupted data, because existed refresh token will be removed when request - but the other API caller still depend on this token to use. So my question : is there anyway to put the request in queue (or at least cancel) all other api request when we got 401 error status code?
This is my authenticator:
public Request authenticate(Route route, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
access_token = getNewAccessTokenHere();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header("Authorization", "Bearer " + access_token)
.build();
} else {
ToastUtil.toast("login again");
return null;
}
}
My goal is let other api waiting for the response of first request and use the new access_token.
I know this question is quite old, but I found myself having the same problem lately and I came by a solution which has worked for me and I believe it could help someone else in the same situation.
The solution to attach a Dispatcher to the OkHttp client and limit the amount of max requests does not seem to work on retrofit out of the box, as stated by Jake Wharthon .
So my solution was to synchronize the authenticate method on my custom Authenticator, making concurrent calls, to my singleton Retrofit instance, wait until the authenticate routine is finished for each thread. This way, the first call with an unauthorized response can refresh the token and inform to the next calls, which also got an unauthorized response, that a new access token is already available.
public class MyAuthenticator implements Authenticator {
private boolean isRefreshed = false;
// Call when a new request is being made. Concurrent request should call this method to enable the refresh routine
public void newRequest(){
isRefreshed = false;
}
#Nullable
#Override
public synchronized Request authenticate(#NonNull Route route, #NonNull Response response) throws IOException { // Synchronize the method to avoid refreshing thread overlapping
if (responseCount(response) > 3) {
return null;
}
if (!isRefreshed){
// Refresh your access_token using a synchronous api request
String accessToken = getNewAccessTokenHere();
// Saves the new access token
saveNewAccessToken(accessToken);
isRefreshed = true;
// Add new header to rejected request and retry it
return response.request().newBuilder()
.removeHeader("Authorization") // removes the old header, avoiding duplications
.addHeader("Authorization", "Bearer " + accessToken)
.build();
}
else{ // Access token already refreshed so retry with the new one
// Get the saved access token
String accessToken = getAccessToken();
return response.request()
.newBuilder()
.removeHeader("Authorization")
.addHeader("Authorization", accessToken)
.build();
}
}
private int responseCount(Response response) {
int result = 1;
while ((response = response.priorResponse()) != null) {
result++;
}
return result;
}
}
You can use the Dispatcher to access all in-flight calls and cancel them.
https://square.github.io/okhttp/3.x/okhttp/okhttp3/Dispatcher.html
I'm using Retrofit2 to make requests to server.
The problem is: sometimes the server will return code 401 for every request from an user. If the user get this code, he should be immediately kicked out from the app (logged out and not be able to do anything before re-login).
So for every request that being sent to the server, I want to check if the server response this code. It's not beautiful writing this check in all the request calls, so I want to write this check only one and it will perform every time user makes request!
Retrofit (current release) needs an HTTP client to make requests. OkHttp library by same developer comes bundled with Retrofit as default client. OkHttp supports adding Interceptor's to the client which can intercept request execution.
For Example:
import android.util.Log;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class ErrorInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
// before request
Request request = chain.request();
// execute request
Response response = chain.proceed(request);
// after request
// inspect status codes of unsuccessful responses
switch (response.code()){
case 401:
// do something else
Log.e("TEST","Unauthorized error for: " +request.url());
// perhaps throw a custom exception ?
throw new IOException("Unauthorized !!");
}
return response;
}
}
To use it, include it in OkHttpClient that Retrofit instance uses:
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new ErrorInterceptor())
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl("/")
.build();
So, you can implement an Interceptor for each "global logic" or "cross-cutting concern" and add them all in a sequence to Retrofit.
If you need check "401" code there is special object in OkHttp for it: Authenticator (Recipes in OkHttp). For example:
public class RefreshTokenAuthenticator implements Authenticator {
#Override
public Request authenticate(Route route, Response response) throws IOException {
// You get here, if response code was 401.
// Then you can somehow change your request or data in your app in this method and resend your request.
Request request = response.request();
HttpUrl url = request.url().newBuilder()
.setQueryParameter("access_token", "new_access_token_may_be")
.build();
request = request.newBuilder()
.url(url)
.build();
return request;
}
}
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 the server gives me a 403, when it decides that the JWT has expired. In this event, I can ask Auth0 to issue me a new JWT, using the refreshtoken. It means I call the Auth0 backend, pass it the refreshtoken, and it gives me a new JWT, which I can then use in my requests.
My question is, how can I efficiently write this behaviour in all my networking code? I will have a couple of endpoints to talk to, and they all might return the 403.
I am thinking I should first make an interceptor that adds the JWT to all requests.
Then there should be behaviour that detects the 403, quietly does a networkcall to Auth0, retrieving the new JWT. Then the original request should be tried again, with the new JWT in its headers.
So I would prefer to have this 403 handling somewhere invisible to my other code, and definitely not have to rewrite it everywhere.
Any pointers on how to achieve this will be appreciated.
--
To be clear, I am basically looking for pointers on how to achieve this using RxAndroid Observables. When a certain Observable finds the 403, it should 'inject' a new network call.
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;
}
}
I am trying to automatically refresh an auth token if it is expired. I am using the new Interceptor class that was introduced in OkHttp 2.2. In the intercept method I am trying the original request with chain.proceed(request), checking the response code, and if the token is expired I am making a call to a separate Retrofit service, synchronously, to obtain a new token.
The strange thing is, no code past the synchronous call seems to run. If I try debugging with a breakpoint on the synchronous call's line, then do a step-over, I am stopped in Dispatcher.java at :
if (!executedCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
Any idea as to what I might be doing wrong here? I could probably just craft a new request by hand, but I am just kind of curious why a Retrofit call doesn't seem to work here.
My Interceptor:
public class ReAuthInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// try the request
Response response = chain.proceed(request);
// if we receive a "401 - Not Authorized" then refresh the auth token and try again
if (response.code() == 401) {
// get a new auth token and store it
UserToken userToken = MobileClient.getOkraService().login(AuthUtil.getUserToken(MSWorksApplication.getContext()));
AuthUtil.authenticate(MSWorksApplication.getContext(), userToken);
Log.d("TEST", "TEST TEST TEST");
// use the original request with the new auth token
Request newRequest = request.newBuilder().header("Authorization", AuthUtil.getAuthToken(MSWorksApplication.getContext())).build();
return chain.proceed(newRequest);
}
else {
// the auth token is still good
return response;
}
}
}
I had this problem - if you're getting an HTTP response code that would cause Retrofit to call failure() instead of success() an exception will be thrown.
If you wrap
UserToken userToken = MobileClient.getOkraService().login(AuthUtil.getUserToken(MSWorksApplication.getContext()));
in a try/catch(RetrofitError e) you'll be able to execute code after a failure, however I've found this to be quite cumbersome and am still in the process of finding a nicer solution.