What is the right way use refreshToken? - android

I am working with an API that needs for some request an access token. The token get refreshed every 10 minutes, so i need to use a refresh token request. The problem is that the refresh token request requires a valid token that didn't expired yet. To summarize my question, is it normal that the refresh token requires unexpired token or there is another safe way to change the logic of refreshing it?
P.S: In the android app usually to refresh token you need get the first failed request than you request an new one and if you you choose to work with WorkManager you'll need at least 15 minutes for the periodicWorkRequest.

In my case this work me as below:
httpClient.authenticator(new Authenticator() {
#Nullable
#Override
public Request authenticate(Route route, Response response) throws IOException {
String SavedAccessToken = "Bearer "+MyApplication.getLoginUser().getAccessToken();
String responseHeader = response.request().header("Authorization");
if (!SavedAccessToken.equals(responseHeader))
return null;
// request new key
String accessToken = null;
Call<ResponModel<User>> call = RetrofitManager.getRetrofitManager().getApiService().requestAccessToken(MyApplication.getLoginUser());
try {
retrofit2.Response responseCall = call.execute();
ResponModel<User> responseRequest = (ResponModel<User>) responseCall.body();
if (responseRequest != null) {
accessToken = responseRequest.getData().getAccessToken();
User user = new User();
user.setUsername(PrefsUtils.get("UserName","").toString());
user.setAccessToken(accessToken);
user.setRefreshToken(responseRequest.getData().getRefreshToken());
MyApplication.updateUser(user);
}
} catch (Exception ex) {
Log.d("ERROR", "onResponse: " + ex.toString());
}
if (accessToken != null)
// retry the failed 401 request with new access token
return response.request().newBuilder()
.header("Authorization", "Bearer "+ accessToken) // use the new access token
.build();
else
return null;
}
});

Related

Achieving a global force logout using Okhttp's authenticator (along with RetroFit , RxJava and Dagger2)

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.

The user remains in the logged state on the first device when logged in from the second device

I have this Interceptor for send aut token and catch errors:
public class AuthHeaderInterceptor implements Interceptor {
private final UserStorage userStorage;
public AuthHeaderInterceptor(UserStorage userStorage) {
this.userStorage = userStorage;
}
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
String authToken = userStorage.getUserProfile().getAuthToken();
Request request = chain.request().newBuilder().addHeader("X-Authorization", authToken).build();
try {
Response response = chain.proceed(request);
if (response.code() == 401) {
throw new IOException("Authorization token is missing");
}
if (response.code() == 403) {
throw new IOException("Invalid authorization token");
}
return response;
} catch (ConnectException e) {
throw new IOException("Error connecting to server");
} catch (SocketTimeoutException e) {
throw new IOException("Connection timeout");
}
}
}
When user loggin from another device authToken change on the server and first user in loggin state. When first user try make som request or try logout - he get 403 error. I want next - when user get 403 error I want clear user data and redirect user to loggin activity. I have 2 questions:
How cant I redirect user to another activity(StartActivity) from AuthHeaderInterceptor ?
Is it correct at all what I want? How can this be done differently?
Using RxAndroid and retrofit can simplify this process. Use a dependency injector like dagger so that redirecting (mentioned as the first requirement) from [anything] is just abstracted out using Inversion of Control.

OkHttp requests are leaking when refreshing server tokens through interceptors

I use RxJava and Retrofit to execute OkHttp requests. I can see in Android Studio's network profiler that my requests are leaked, because they are keeping the AsyncTask threads alive. No response for them and their size is null. I can see the original request, the token request, the updated request in the Thread View in network profiler. But the original request never finishes.
In extreme cases, like in QA environment where the tokens are refreshed every minute for some reason the threadpool becomes full and no more calls can be made. I think we can call chain.proceed as many times as we want, but I think the error is still somewhere there. Here is the interceptor code:
private static okhttp3.Interceptor oauthInterceptor = chain -> {
OAuthToken token = OAuthToken.getOAuthToken();
long initialTokenCreated = token.getCreatedUtc();
Request request = changeTokenInRequest(chain.request(), token);
Response response = chain.proceed(request);
boolean forceTokenRefresh = false;
if (initialTokenCreated != token.getCreatedUtc()) {
// Then the token has been updated since we started this request
request = changeTokenInRequest(chain.request(), token);
response = chain.proceed(request);
}
String jsonType = "application/json";
if (!response.body().contentType().toString().contains(jsonType)) {
forceTokenRefresh = true;
}
// 401: Forbidden, 403: Permission denied
if (forceTokenRefresh || ((response.code() == 401 || response.code() == 403)
&& OAuthToken.getOAuthToken().getRefreshToken() != null)) {
OAuthToken refreshedToken = refreshToken();
if (refreshedToken == null) {
// Then there was a problem refreshing the token
return response;
}
// Recreate the request with the new access token
request = changeTokenInRequest(chain.request(), refreshedToken);
return chain.proceed(request);
} else {
return response;
}
};
protected static okhttp3.Request changeTokenInRequest(Request request, OAuthTokenBase token) {
Headers.Builder builder = request.headers().newBuilder().removeAll("Authorization");
if (!TextUtils.isEmpty(token.getTokenType()) && !TextUtils.isEmpty(token.getAccessToken())) {
builder = builder.add("Authorization", token.getTokenType() + " " + token.getAccessToken());
}
request = request.newBuilder().headers(builder.build()).build();
return request;
}
That's how I make the calls:
public static Observable<ResultPaginatedReply<BasicUser>> getGroupsMembers(String type, String id, int page) {
return getMsApiService().getGroupsMembers(type, id, page)
.subscribeOn(Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR))
.observeOn(AndroidSchedulers.mainThread());
}
Turns out that if you don't use the Authenticator class in OkHttp like me here, than you have to manually close the response body when the token is refreshed. #DallinDyer's comment helped me here.
So I added
response.body().close();
after the error checks and voilĂ .

How to manage token authentication in android app

I am using JWT to manage server authentication for my application. I manage it in the server. In my angular app, I am using angular-jwt to manage jwt. If it expired how to call refresh token API. I am using volley for API call. When the token is expired the server responds with 401 error code. That time needs to call a refresh token api. After refresh call, i need to call the previous API call again without any user input.
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest
(method, request_url, null, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
try {
if (response.getInt("status") == 1) {
callback.onSuccessResponse(response.get("data"));
} else {
callback.onSuccessResponse(new JSONArray());
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
NetworkResponse networkResponse = error.networkResponse;
if (networkResponse != null && networkResponse.statusCode == 401) {
}
}
}) {
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
String token = AppPreferencesHelper.getAccessToken(context);
headers.put("Authorization", "Bearer " + token);
return headers;
}
};
Volley.newRequestQueue(context).add(jsonObjectRequest);
You need to call api to validate token, if its expired than you can generate new.
Or if you have login feature than you can check for JWT in login and generate new if old expired.
I have use a very basic way in my app, I am creating a JWT using a unique email address per user and stores it into database in server and it never expires. Whenever a user logout, I remove the JWT and when login again I generate new token.

Put all ongoing requests to queue retrofit and okhttp

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

Categories

Resources