Retrofit & RxJava2 refresh token and retry - android

I have a login endpoint by which I receive an authToken and a refreshToken. The first one expires in an hour, so I should use the second one to refresh it and continue the application flow.
Now my app is filled with retrofit calls all over the place and I potentially could get a 401 at any given moment, so how can I make it so every time I get a 401 a refresh token is issued, and then the original request retried?
This is my refresh signature:
#POST("/auth/actions/refresh")
fun refreshToken(#Body tokenRefresh: TokenRefresh): Single<LoginResponse>
I thought about making a base repository class with a method withAuth() that takes any Observable/Single/Flowable and then applies this logic but I cannot find a way to implement it.
Saw a bunch of implementations but none of them match my needs... can anyone push me in the right direction?
This is the closest I've found, however there seems to be some errors on the flatmapping

I just came across similar requirement and came up with following solution. It's pretty simple, it just makes one attempt to call REST endpoint and if that call fails with HTTP 401 it reauthenticates and repeats the call again. Otherwise it just emits the original error.
fun <T> Single<T>.withAuth() = retryWhen { errors ->
var firstAttempt = true
errors.flatMapSingle { error ->
if (firstAttempt && error is HttpException && error.code() == 401) {
firstAttempt = false
reauthenticate()
} else {
Single.error(it)
}
}
}
where the reauthentication function has the following signature:
fun reauthenticate(): Single<AnyAuthResponse>
Please note that the concrete exception type might depend on HTTP implementation you actually use, so you may want to update the condition to detect HTTP 401 response, but the code should give you an overall picture of how to solve your problem.

I think you can do this without modifying all calls. Add an Authenticator to your Retrofit
Refreshing OAuth token using Retrofit without modifying all calls

You can use Interceptor to intercept each request and check whether it returns 401 - UnAuthorised Access and iff then refresh the token and replay the current API request.
public final class SessionInterceptor implements Interceptor {
// gets intercept
#Override public Response intercept(#NonNull final Chain chain) throws IOException {
final Request request = chain.request();
final Response response = chain.proceed(request);
final ResponseBody responseBody = response.body();
if (response.code() == 401) {
synchronized (this) {
// Refresh your token
// Update your authToken + Refreshed token
final retrofit2.Response response = refreshToken();
}
}
// Replay the original request
// Perform request, here original request will be executed
final Request original = chain.request();
final Request.Builder builder = original.newBuilder();
// Set your new refreshed token
if (accessToken.isSet()) {
builder.header(AUTHORIZATION, String.format(BEARER,
accessToken.get()));
}
final Request request = builder.method(original.method(), original.body()).build();
return chain.proceed(request);
}
}

Related

Continue executing a chained RxJava observable on error, stop executing only when specific condition is met

How to continue executing a Retrofit network call observable that takes in as items as input obtained from another observable that is converted to flatMapIterable even if an error was encountered and stop iterating only if I encounter a specific HTTP status code?
I have a list of JSON requests saved in shared preferences, that I need to send one by one using Retrofit, only stopping whenever I get a certain HTTP status code. If I get other exceptions, I just need to continue sending the next items in the requests list. Whenever a request receives a successful response, I remove that particular request from my request list. If other requests have encountered errors, they do not get removed from the list and I save them again to the shared preferences.
So far, I do this inside a ViewModel object. First, I fetch these requests via a method (paramRepository.getSavedOfflineRequest()) that returns an RxJava Observable<List<Request>>. I want to iterate through all the requests so that I can send the items as inputs to apiService.sale, which is my Retrofit call, so I use flatMapIterable. If the request is successful, I remove the request and save a Transaction object to DB.
public LiveData<UploadStatus> startUploading() {
MutableLiveData<UploadStatus> uploadStatus = new MutableLiveData<>();
compositeDisposable.add(paramRepository.getSavedOfflineRequest()
.doOnComplete(() -> uploadStatus.setValue(UploadStatus.NO_ITEMS))
.flatMapIterable( requests -> requests)
.flatMapCompletable(request -> apiService.sale(saleUrl, BuildConfig.ApiKey,request)
.doOnSuccess(response -> {
requestList.remove(request);
transactions.add(createTransaction(request, response));
}).ignoreElement()
)
.andThen(saveUploadedToDb(transactions))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> uploadStatus.setValue(UploadStatus.SUCCESS),
error -> {
Log.d(TAG, "error");
if (error instanceof HttpException) {
HttpException httpException = (HttpException) error;
int statusCode = httpException.code();
if (statusCode == 401) {
Log.d(TAG, "logged out");
uploadStatus.setValue(UploadStatus.LOGGED_OUT);
}
} else {
uploadStatus.setValue(UploadStatus.FAIL);
}
}));
return uploadStatus;
}
I expect that if if I get other errors/exceptions, I just continue making calls using apiService.sale with the next Request item. But I noticed that the whole chain stops when just one error is encountered, thus the other Requests have not been sent.
I have tried onErrorResumeNext, but it expects a return of another kind of Exception, which is totally different from what I want (do nothing for other Exceptions).
You probably want the observable to return UploadStatus and map the response.
E.g.
.map { response ->
switch(response.code()) {
case 200:
return UploadStatus.SUCCESS;
case 401:
return UploadStatus.LOGGED_OUT;
default:
return UploadStatus.FAIL;
}
}
In the case of an exception, you can use onErrorReturn to return UploadStatus.FAIL/ERROR. This won't terminate the stream.
.onErrorReturn { UploadStatus.FAIL }

Using MockWebServer 's dispatcher in espresso test to respond to an Async call from another module

I'm writing UI tests for an app that expects a JSON payload delivered as a response to a request made via an Async call which is triggered by a button in the activity I'm testing. I intend to provide various mock payloads to set the state of this activity and assert various items in the UI.
I'm not having any luck with getting the dispatcher to catch the request made via the async call.
The async call is made in a class within a module which is a compile time dependency for my application and I feel like this is where my problem lies, and am not even sure if that is outside the scope of Espresso's abilities.
#Before
public void setup() throws Exception {
server = new MockWebServer();
server.start();
// setting API in my production code to the MockWebServer
settingsProvider.setWebServiceURL(server.url("/").toString());
server.setDispatcher(new Dispatcher() {
#Override
public MockResponse dispatch(RecordedRequest recordedRequest) throws InterruptedException {
if (recordedRequest.getPath().startsWith("/v1/customers")) {
return new MockResponse().setResponseCode(200).setBody(JSONstring);
}
return new MockResponse().setResponseCode(204);
}
});
}
If I make a test request from within my actual test, it works:
#Before
public void testGet(){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(server.url("/v1/customers").toString()).build();
Log.d(LOG_TAG, "Test send GET " + request);
try {
Response response = client.newCall(request).execute();
} catch (IOException e) {
e.printStackTrace();
}
}
By adding logs I've verified that my API url is being set correctly and is the same port as the MockWebServer. The app does not not current use any dependency injection.
Main part of async call:
public void makeAsyncCall(final Application ctx , final AsyncWebServiceRequest request, final ResponseListenerInterface rli){
final AsyncWebServiceResponseListener listener = new AsyncWebServiceResponseListener(ctx, request, rli);
IntentFilter responseIntent = new IntentFilter(CommsActions.SEND);
responseIntent.addCategory(request.getMessageType());
ctx.registerReceiver(listener, responseIntent);
try {
ctx.startService(intent);
} catch (Exception e) {
Log.e(LOG_TAG , Log.getStackTraceString(e));
);
}
}
Appreciate any input.
Long story short - I didn't realise that I was actually trying to mock an AMQP call, rookie move.
Luckily I was able to simplify the code by converting these to HTTP requests using retrofit as the application only really requires the sending of data to be performed over AMQP while retrieving can be a simple HTTP GET, which allowed for easy implementation of mockwebserver and actually sped up requests.

Handle Background Refresh token call in Retrofit parallel network calls

I am new to android programming and Retrofit , I am making a sample app where i have to make two parallel network calls using access token.
The problem comes when access token is expired and return 401 status code , if I see 401 HTTP status code I have to make a call to refresh token with this access token , but problem with parallel calls is that it leads to race condition for refreshing the refresh token , is there any best practice of way to avoid such situation and how to intelligently refresh the token without any conflict.
OkHttp will automatically ask the Authenticator for credentials when a response is 401 Not Authorised retrying last failed request with them.
public class TokenAuthenticator implements Authenticator {
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
// Null indicates no attempt to authenticate.
return null;
}
Attach an Authenticator to an OkHttpClient the same way you do with Interceptors
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(authAuthenticator);
Use this client when creating your Retrofit RestAdapter
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(ENDPOINT)
.setClient(new OkClient(okHttpClient))
.build();
return restAdapter.create(API.class);
Check this: Fore more details visit this link
Try to make a queue for the refresh token operations like:
class TokenProcessor {
private List<Listener> queue = new List<Listener>();
private final Object synch = new Object();
private State state = State.None;
private String token;
private long tokenExpirationDate;
public void getNewToken(Listener listener){
synchronized(synch) {
// check token expiration date
if (isTokenValid()){
listener.onSuccess(token);
return;
}
queue.add(listener);
if (state != State.Working) {
sendRefreshTokenRequest();
}
}
}
private void sendRefreshTokenRequest(){
// get token from your API using Retrofit
// on the response call onRefreshTokenLoaded() method with the token and expiration date
}
private void onRefreshTokenLoaded(String token, long expirationDate){
synchronized(synch){
this.token = token;
this.tokenExpirationDate = expirationDate;
for(Listener listener : queue){
try {
listener.onTokenRefreshed(token);
} catch (Throwable){}
}
queue.clear();
}
}
}
This is an example code, how it can be implemented.
To avoid race conditions, you could synchronize refresh token code using ReentrantLock. For instance, if request A and request B try to refresh token at the same time, since code is synchronized, refresh A gets to actually refresh the token. Once it completes, request B will run refreshToken() and there's should be some logic that tells request B that token has already been refreshed. An example could be storing timestamp of when the token refresh happens then check if token has been refreshed last 10 seconds.
val lock = ReentrantLock(true)
fun refreshToken(): Boolean {
lock.lock()
if (token has been refreshed in last 10 seconds): return true
api.refresh()
lock.unlock()
}
If you don't want to use last 10 seconds logic, here's a different approach. Whenever you refresh token, backend returns {accessToken, expiration-timestamp}. Now, request A saves this token and expiration in disk. Request B will just need to check to make sure token is not expired using the timestamp. If request B gets 401 and token has not expired, it means request A has refreshed the token. Sample code:
val lock = ReentrantLock(true)
fun refreshToken(): Boolean {
lock.lock()
if (token has not expired): return true
api.refresh()
lock.unlock()
}
Otherwise, you probably have to create a queue for refresh token operations as mentioned above.

Merge and handle two RxJava Observable of different types

My goal
I want to check if the server's token is still valid, let's say I know that information just by calling this getter : preferenceHelper.isTokenValid(). Then, if the token is invalid, calling a request to get a new token and updating the token locally, THEN, proceed with the next request to post the point to the server. That's because I need a valid token in order to make any further server request.
Let say I have those two server request that returns Observable:
This request is meant to get the server token, then upon reception, updating it.
Observable<Response<EntityToken>> updateServerToken = retrofitApi.authenticate(username,password);
This request is meant to post the current location to the server, then if it succeed, return the saved point
Observable<Response<EntityPoint>> updateServerToken = retrofitApi.postPoint(point);
Issues i'm facing currently:
Both observable that needs to be merged are from different type
Executing the token update request only if it needs to
Waiting for the token update request to complete before executing the request to post points
How should I write my RxJava Observable to satisfy all those condition?
First, I would create a method that checks if the entityToken is valid or not. If valid, use Observable.just() but you have to create an instance of Response somehow. If invalid, then call the server using the API in your requirement retrofitApi.authenticate(). Either path is taken, the method getTokenObservable() emits Observable<Response<EntityToken>>.
public Observable<Response<EntityToken>> getTokenObservable(EntityToken entityToken, String username, String password) {
boolean isTokenValid = preferenceHelper.isTokenValid(entityToken);
if (isTokenValid) {
//my assumption that you have something like this
Response<EntityToken> responseToken = new Response<EntityToken>();
responseToken.setEntityToken(entityToken);
return Observable.just(new Response<EntityToken>(entityToken.class));
} else {
Observable<Response<EntityToken>> updateServerToken = retrofitApi.authenticate(username, password);
return updateServerToken;
}
}
and then when calling it, use flatMap() which take emisssions of Observable<Response<EntityToken>> and returns emissions of Observable<Response<EntityPoint>>. Subscribe and proceed as normal.
Observable<Response<EntityToken>> updatePointObservable = getTokenObservable(entityToken, username, password);
updatePointObservable
.flatMap(new Func1<Response<EntityToken>, Observable<Response<EntityPoint>>>() {
#Override
public Observable<Response<EntityPoint>> call(Response<EntityToken> responseToken) {
EntityToken entityToken = responseToken.getEntityToken(); //my assumption
saveTokenLocally(entityToken); //this is where you save your token locally, change to the right method that you have
Observable<Response<EntityPoint>> updateServerTokenObservable = retrofitApi.postPoint(point, entityToken); //pass your entityToken to the call?
return updateServerTokenObservable;
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Response<EntityPoint>>() {
#Override
public void onCompleted() {
//your own logic
}
#Override
public void onError(Throwable e) {
//your own logic
}
#Override
public void onNext(Response<EntityPoint> entityPoint) {
//your own logic
}
});
As there is a dependency between the three calls, merge does not make any sense. instead, use flatMap:
Observable<Response<EntityPoint>> response =
retrofitApi.isTokenValid()
.flatMap(isValid ->
isValid
? Observable.just("")
: retrofitApi.authenticate(username,password)
.doOnNext(token -> doSomethingWithTheToken(token)
)
.flatMap(dummy -> retrofitApi.postPoint(point));

Blocking request interceptor with Retrofit?

Is there a nice way to implement "blocking" request interceptor?
The main idea is that all requests should be intercepted and added additional header - token.
If token does not exist yet it should be retrieved first, then added to that request and cached for future used. token is retrieved via API call.
I've tried to do synchronous request, but, that produces android.os.NetworkOnMainThreadException. And implementing with in_progress flags it doesn't look nice.
You can already do the 'intercept' part of this using RequestInterceptor. Just use RestAdapter.Builder.setRequestInterceptor().
It's a better idea to retrieve the token from the API outside the RequestInterceptor though, as it's not meant to do that. After that first call, you can just add the token anywhere you want in your requests inside RequestInterceptor.intercept().
Something like this:
Builder builder = new RestAdapter.Builder()
//Set Endpoint URL, Retrofit class... etc
.setRequestInterceptor(new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
String authToken = getAuthToken(); //Not included here, retrieve the token.
request.addHeader("Authorization", "Bearer " + authToken);
}
);
Well, you have already implemented your 'blocking' interceptor, your problem is android doesn't let you block the main thread with network calls.
You should probably wrap your retrofit calls in a service class that calls, asynchronously, to your getToken method, and makes the 'main' request only if and when that first one completes succesfully.
As of OkHTTP 2.2, you can now add interceptors that run on the network thread:
https://github.com/square/okhttp/wiki/Interceptors
An example interceptor for adding an auth token might be like this;
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Get your auth token by going out over the network..
// add authorization header, defaulting to the original request.
Request authedRequest = request;
if (!TextUtils.isEmpty(authToken)) {
authedRequest = request.newBuilder().addHeader("Auth", authToken).build();
}
return chain.proceed(authedRequest);
}

Categories

Resources