Android JWT refresh token flow - android

I'm implementing codes with jwt on Android.
At point of using refresh token, I'm not sure my code is correct way.
Here is sequene diagram of my flow.
Server issued access token and refresh token. These expire time is 1hour and 3 days. These token is saved to sharedpreferences.
Here is above diagram's description.
When access token is expired, http call will be failed with 401 error.
So I implemented getAccessToken() for re-newing access token.
(1) : One AsyncTask is used for this whole http call step.
- My AsyncTask is too big, I want to refactor it.
(2) : (1)'s AynsTask has a logic for re-getting access token.
- This logic was duplicated all my HTTP call functions.
(3) : After renewing access token, my app re-try to call /api/foo
- To retry it, AsyncTask's doBackground() function is call recursivly.
Here is my code snippet.
class ApplyCheck extends AsyncTask<String, Void, ResponseTypeEnum> {
private List<ApplyEntity> applyEntityList = null;
#Override
protected ResponseTypeEnum doInBackground(String... strings) {
try {
response = restManager.getApplyList(strings[0],"","",""); // call /api/foo
} catch (RestRuntimeException e) {
return ResponseTypeEnum.SERVER_ERROR;
}
switch (response.code()) {
case 200:
//set applyEntityList
....
return ResponseTypeEnum.SUCCESS;
case 401:
//<-- This routine is duplcated all my AsyncTasks
if(getAccessToken()) {
//<-- recursive call to re-call api
return doInBackground(strings);
} else {
return ResponseTypeEnum.TOKEN_EXPIRE;
}
}
}
//re-issue new access token
private boolean getAccessToken() {
Response response = restManager.getAccessToken(); // call /auth/issue-token
if(response.code() == 200) {
String tokens = response.body().string();
JSONObject jsonObject = new JSONObject(tokens);
sharedPreferences.edit().putString("accessToken", jsonObject.getString("accessToken"));
sharedPreferences.edit().putString("refreshToken", jsonObject.getString("refreshToken"));
return true;
} else {
return false;
}
My Questions
1. Is my approach correct? If not, please inform me good practice.
2. If yes, are any good practice for extracting common function for my duplicated AsyncTasks?

The process you have is fine IMHO. The only change is that I would not recursively call doInBackground. What you're doing is feasible, but it violates the intention of doInBackground. Rather modify your AsyncTask to cope with processing different responses in onPostExecute, (ie chaining your requests), and call the AsyncTask again with the relevant parameters for each use case. It will make it much easier to maintain as you can add specific methods to the AsyncTask to cope with each response type and can see how it's triggered in a linear way. If you need to update onProgressUpdate, you should also pass a progress value to the chained AsyncTask calls so it can maintain consistency on the progress. Otherwise it would keep restarting on each call.

Related

Tokens and how to handle them

I'm developping an app that pulls and push data to a server.
To prevent undesired use of the webservices, we settled a token system that renews every once in a while.
I'm creating a method that would check before every webservices calls if the token is not null.
Something like :
public boolean isTokenValid(){
if (token == null){
renewToken();
return false;
}
return true;
}
Same fashion if I get server error code that the token is expired I'd call renewToken()
My question :
How do I have my method to be called again when receiving the valid token ? Keep in mind the method called after is "dynamic" : the method could be called from any webservice and therefore require it to run this very call again.
Potential solutions :
1) Create as many method of token renewal as the webservices.
For eg : renewTokenGetProduct(), renewTokenGetProfile()... to know which webservice to call when getting valid token. But that seems rather inapropriate.
2) I could also be passing a parameter. For example renewToken(PRODUCT) or renewToken(PROFILE) and then use conditions or broadcast but it seemed to me a bit overkill/sloppy again.
From api side try to add one more parameter with the token.
Like token and category.
so
public boolean isTokenValid(){
if (token == null){
renewToken();
return false;
}
return true;
}
then in renew token:
if(category.equals("profile"){
//do necessary work
}
else if(category.equals("product"){
//do necessary work or pass intent
}
else{
//
}

How to terminate sequence of switch maps in RxJava2 on error

I am trying to implement a refresh token flow in RxJava2 and Kotlin and I have trouble handling errors. There are several requests that need to be done in sequence but the sequence differs in case there are some errors.
Basically, if I try to use my refresh token and receive 400 - Bad Requestresponse because the token is not valid, I need to terminate the flow and do not execute the next switchMap (and ideally, I would like to return the final Observable<ApplicationRoot>). But I am not sure how to achieve this.
If I use onErrorReturn, I will just pass the returned result to the next switch map. And doOnError just executes the step when the request fails but the whole sequence continues.
fun refreshToken(): Observable<ApplicationRoot> {
// try to use the refresh token to obtain an access token
return authRepository.refreshToken(token)
.switchMap { response ->
// process response here
userRepository.getUser() // fetch user details
}.doOnError {
// TODO - return final result, do not jump to next switchMap
// refresh token is not valid -> go to login screen
Observable.just(ApplicationRoot.LOGIN) // not working
}.switchMap { response -> // excpects response of type UserResponse
// save user details here
}
}
Does anyone know who to jump out of the sequence of switch maps if some error occurs?
Probably, you should do something like this:
fun refreshToken(): Observable<ApplicationRoot> {
return authRepository.refreshToken(token)
.switchMap { response ->
userRepository.getUser()
}
.switchMap { response -> // do something with user response
}
.map { ApplicationRoot.ROOT_THAT_MEANS_SUCCESS }
.onErrorResumeNext(Observable.just(ApplicationRoot.LOGIN))
}
I am not aware about implementation of authRepository.refreshToken and how do responses look like, but in case if response is your custom object rather than retrofit2.Response<T> it should work.

Unable to implement callback in Android

I am building an SDK.
The Scenario
From the app, the developer passes the JsonObject and URL in a method
and inside the SDK.
I add those values in SQLite database and start a JobScheduler.
The Jobscheduler takes the request at 0 indexes out of the database
executes it.
When I get the response, I delete that request from the database and now the request at 1 index comes to the 0 index and again I execute the same code where 0th index request is fired.
The Problem
When I get the response from a server inside the SDK, I need to send it to the developer using a callback.
I can take the callback as an argument when I take the JSON and URL from user, but I don't know how to proceed further because I cannot store it into the database
Suppose I have 5 requests in the database and scheduler executes it one by one, I don't know how to send the response back to the developer. I can not pass context in the jobscheduler. The only way to do that is I get the corresponding context for each row (request) in the database.
What I tried
I tried using a LocalBroadcastManager, but I can not create a generic class and get it's onreceive() method implemented, also the context passing was a problem
Tried using Realm as a database so that I can add Context type and use my model, but it is not working as Context is not supported.
Storing Activity name in the database with other details and then driving out the class from it and activity from class. And then typecasting the activity into my callback. but it throws ClassCastException as the class it tries to derive from the Name is on the developer's app and not in my SDK
..
The App's MainActivity Code
sdkClass.queueRequest(jo.toString(),"url1",12,"MainActivity");
sdkClass.queueRequest(jo2.toString(),"url2",13,"MainActivity");
sdkClass.queueRequest(jo.toString(),"url3",14,"MainActivity");
sdkClass.executeQueue();
The SDK class which adds and executes code
public void queueRequest(String payload, String URL, int requestId, String identifier){
RequestsDatabase database = new RequestsDatabase(context);
database.insertItem(TxnType,payload,url, String.valueOf(requestId), identifier);
}
public JobScheduler getQueueInstance(Context context){
if(jobScheduler==null){
jobScheduler = (JobScheduler)context.getSystemService(JOB_SCHEDULER_SERVICE);
}
return jobScheduler;
}
public void executeQueue(){
getQueueInstance(context);
ComponentName jobService = new ComponentName(context.getPackageName(), MyJobService.class.getName());
JobInfo jobInfo = new JobInfo.Builder(1,jobService).setPersisted(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY). build();
jobScheduler.schedule(jobInfo);
}
MyJobServiceCode
#Override
public boolean onStartJob(JobParameters params) {
// UtilityMethods.showToast(this,params.getExtras().getString("json"));
Toast.makeText(this,"test", Toast.LENGTH_SHORT).show();
database = RequestsDatabase.getInstance(this);
ALlRequests = database.getAllItemId();
executeCall();
return false;
}
public void executeCall(){
ALlRequests = database.getAllItemId();
if(ALlRequests.size()>0) {
requestModel = ALlRequests.get(0);
try {
String payload = getPayload(this, requestModel.getTxnType(), requestModel.getPayload());
SilverLineRequest req = new SilverLineRequest(this, requestModel.getUrl(), payload, Integer.parseInt(requestModel.getRequestId()), this);
req.executeToServer();
Toast.makeText(getApplicationContext(), "requested", Toast.LENGTH_LONG).show();
} catch (JSONException e) {
e.printStackTrace();
}
}
else{
Toast.makeText(this, "Queue empty", Toast.LENGTH_LONG).show();
}
}
#Override
public void onSuccess(String response, int requestId) {
if(requestId ==Integer.parseInt(requestModel.getRequestId())){
Toast.makeText(this,"responded", Toast.LENGTH_SHORT).show();
database.deleteItem(requestModel.getTxnType());
// HERE I WANT TO SEND THE USER RESPONSE LIKE
// listener.onTransactionSuccess(response)
// BUT I DON'T HAVE 'listener' SO IT SHOWS NULLPOINTER
SDKClass sl = new SDKClass(this);
sl.executeQueue();
}
}
Any help will be blessed right now.
You store requests in database, so it can has unique id (autoincrement).
Then you can store callbacks in memory with relation request id -> callback.
On response you can call it.
If you want, return id to developers. Then they can bind and unbind and get results when they need, even the response had got yesterday.
Take a look on TransferUtility from Amazon AWS SDK for example.

Is it possible to send a synchronous request in the Firebase?

I'm using Firebase Android SDK and became interested in sending synchronous request instead of asynchronous. According to the documentation, in any request callbacks are presented. But what about the synchronicity?
Thanks!
There is no way to synchronously load data from the Firebase Database.
While it is common for developers new to Firebase to wish for a synchronous method, it simply doesn't fit with Firebase's data synchronization model. Also see my answer here: Setting Singleton property value in Firebase Listener
It is not possible to load data synchronously with the official SDK. However, you can access all the data in firebase using the REST API. This would allow you to make synchronous calls. As mentioned about, Firebase is a realtime database and you will be missing the feature of updates when your data changes.
I made a simple class to call tasks synchronously in Android.
Note that this is similar to Javascript's async await function.
Check my gist.
TasksManager.class
public class TasksManager {
...
public ExecutorService getExecutor() {
if (mDefaultExecutor == null || mDefaultExecutor.isShutdown() || mDefaultExecutor.isTerminated()) {
// Create a new ThreadPoolExecutor with 2 threads for each processor on the
// device and a 60 second keep-alive time.
int numCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
numCores * 2,
numCores * 2,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>()
);
mDefaultExecutor = executor;
}
return mDefaultExecutor;
}
public static <TResult> Task<TResult> call(#NonNull Callable<TResult> callable) {
return Tasks.call(getInstance().getExecutor(), callable);
}
}
Here's a sample code to use it.
TasksManager.call(() -> {
Tasks.await(AuthManager.signInAnonymously());
// You can use multiple Tasks.await method here.
// Tasks.await(getUserTask());
// Tasks.await(getProfileTask());
// Tasks.await(moreAwesomeTask());
// ...
startMainActivity();
return null;
}).addOnFailureListener(e -> {
Log.w(TAG, "signInAnonymously:ERROR", e);
});
While it is not possible to load data from the FirebaseDatabase in a synchronous way, it is possible to wait for the load to finish synchronously.
You can wrap your value listener in a CountDownLatch and count down,
once the onDataChange or onCancelled implementation is called.
This is actually what the Tasks api is doing internally if you call Tasks.await(someTask).
You should use the value listener for single event listening, because in this case I assume you don't want continued updates. And use a proper timeout for the CountDownLatch, since Firebase won't timeout, ever.
reference.addListenerForSingleValueEvent(...);
You also have to take into account, that if you have the FirebaseDatabase
cache enabled, the first result might not be the actual value on the
server.
I have to add: While this might work, it is against the idea how firebase is designed and supposed to be used, as Frank already said.
If you are using Kotlin, add an extension function:
private suspend fun <TResult> Task<TResult>.await(): TResult? {
return try {
Tasks.await(this)
} catch (e: Exception) {
null
}
}
Now you can do
val snapshot = fireStore.collection(USER_ROOT_PATH).document(documentPath)?.get()?.await()

Conditional subsequent request with RxJava

I have an issue with my network client design. I have a use case, when the client tries to request an item from a REST API, but in case the API returns a 404 HTTP status code I need to send a request to create the item on the server and then request the item again.
I would like to use RxJava to avoid the callback hell. Is this a valid use case RxJava? Is it possible to create such a conditional sub-request?
Thank you for your time and answers.
Based on your question, I assume you have something that look like
public Observable<Item> getItem();
that will either return the item, or fire an error and
public Observable<?> createItem();
That will create one.
You can use those two together like so:
public Observable<Item> getOrCreateItem() {
return getItem().onErrorResumeNext(error -> {
// Depending on your framework, figure out which is the result code
if (error.getResultCode() == 404) {
return createItem().flatMap(ignored -> getItem());
} else {
return Observable.error(error);
}
});
}
With Retrofit, you'd have to simply make sure the exception is a RetrofitError, cast it, and get the response and the status code. (((RetrofitError) error).getResponse().getStatus())

Categories

Resources