Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Repeat a request after getting a fresh JWT token on 401 #50

Closed
ToniTornado opened this issue Aug 12, 2018 · 19 comments
Closed

Repeat a request after getting a fresh JWT token on 401 #50

ToniTornado opened this issue Aug 12, 2018 · 19 comments

Comments

@ToniTornado
Copy link

I'm considering to switch my app repositories to use dio for all http requests, it looks really promising!

What is the best approach to refresh a JWT token with dio? Is there an easy way to get a fresh token and repeat the request with the fresh token after it got a 401?

@zinwalin
Copy link

same issue here.

@ToniTornado
Copy link
Author

Another idea is to decode the JWT token in an interceptor and get it's expiry date; if it is expired or about to expire, it could be refreshed and replaced before the request continues. Sounds like a clean solution, but I haven't tried it yet. As I said, I'm still evaluating which library to use.

And I was curious to hear about experiences from others how to deal with it.

@wendux
Copy link
Contributor

wendux commented Aug 14, 2018

@ToniTornado @zinwalin Have you read this example?
https://github.com/flutterchina/dio/blob/flutter/example/interceptorLock.dart , Similarly, you can repeat the request with a new dio instance after you retrieved the new token in onError, like:

  String csrfToken; //top-level variable
  dio.interceptor.response.onError = (DioError error) {
    if(error.response?.statusCode==401){
      Options options=error.response.request;
      // If the token has been updated, repeat directly.
      if(csrfToken!=options.headers["csrfToken"]){
        options.headers["csrfToken"]=csrfToken;
        //repeat
       return  dio.request(options.path,options: options);
      }
      // update token and repeat
      // Lock to block the incoming request until the token updated
      dio.interceptor.request.lock();
      dio.interceptor.response.lock();
      return tokenDio.get("/token").then((d) {
        //update csrfToken
        options.headers["csrfToken"] = csrfToken = d.data['data']['token'];
      }).whenComplete((){
        dio.interceptor.request.unlock();
        dio.interceptor.response.unlock();
      }).then((e){
       //repeat
       return dio.request(options.path,options: options);
      });
    }
    return error;
  };

:octocat: From gitme Android

@wendux wendux closed this as completed Nov 19, 2018
@Xgamefactory
Copy link

@ToniTornado @zinwalin Have you read this example?
https://github.com/flutterchina/dio/blob/flutter/example/interceptorLock.dart , Similarly, you can repeat the request with a new dio instance after you retrieved the new token in onError, like:

  String csrfToken; //top-level variable
  dio.interceptor.response.onError = (DioError error) {
    if(error.response?.statusCode==401){
      Options options=error.response.request;
      // If the token has been updated, repeat directly.
      if(csrfToken!=options.headers["csrfToken"]){
        options.headers["csrfToken"]=csrfToken;
        //repeat
       return  dio.request(options.path,options: options);
      }
      // update token and repeat
      // Lock to block the incoming request until the token updated
      dio.interceptor.request.lock();
      dio.interceptor.response.lock();
      return tokenDio.get("/token").then((d) {
        //update csrfToken
        options.headers["csrfToken"] = csrfToken = d.data['data']['token'];
      }).whenComplete((){
        dio.interceptor.request.unlock();
        dio.interceptor.response.unlock();
      }).then((e){
       //repeat
       return dio.request(options.path,options: options);
      });
    }
    return error;
  };

:octocat: From gitme Android

such method wont working properly when multiple requests enqueued.
see #590

@JDChi
Copy link

JDChi commented May 26, 2020

@ToniTornado @zinwalin Have you read this example?
https://github.com/flutterchina/dio/blob/flutter/example/interceptorLock.dart , Similarly, you can repeat the request with a new dio instance after you retrieved the new token in onError, like:

  String csrfToken; //top-level variable
  dio.interceptor.response.onError = (DioError error) {
    if(error.response?.statusCode==401){
      Options options=error.response.request;
      // If the token has been updated, repeat directly.
      if(csrfToken!=options.headers["csrfToken"]){
        options.headers["csrfToken"]=csrfToken;
        //repeat
       return  dio.request(options.path,options: options);
      }
      // update token and repeat
      // Lock to block the incoming request until the token updated
      dio.interceptor.request.lock();
      dio.interceptor.response.lock();
      return tokenDio.get("/token").then((d) {
        //update csrfToken
        options.headers["csrfToken"] = csrfToken = d.data['data']['token'];
      }).whenComplete((){
        dio.interceptor.request.unlock();
        dio.interceptor.response.unlock();
      }).then((e){
       //repeat
       return dio.request(options.path,options: options);
      });
    }
    return error;
  };

:octocat: From gitme Android

such method wont working properly when multiple requests enqueued.
see #590

I also met the same question . In my project , inside "onError" , dio.interceptor.errorLock.lock(); call multipletime.

@giandifra
Copy link

giandifra commented Jun 3, 2020

Same problem, refresh code enter into loop of requests

@Fleximex
Copy link

Fleximex commented Jul 22, 2020

The following solution/hack seems to work for me. Just keep track of a number (maybe a boolean would also work). When a request is successful or when there is an error while the counter is uneven (1) set the _repeatCounter to 0 (even number).
With the status code responses of the requests you want to repeat, in my case 401, you up the counter (to 1) before retrying the request.

The code is not battle tested (what happens i.e. when you make multiple requests simultaneously?).
But it seems to work well enough.

int _repeatCounter = 0;

...

dio.interceptors.add(InterceptorsWrapper(onError: (error) async {
    if (error.response.statusCode == 401 && _repeatCounter.isEven) {
        dio.interceptors.requestLock.lock();
        dio.interceptors.responseLock.lock();
        await refreshCredentials();
        dio.interceptors.requestLock.unlock();
        dio.interceptors.responseLock.unlock();
        _repeatCounter++;
        return await dio.request(
            error.request.path,
            queryParameters: error.request.queryParameters,
            data: error.request.data,
            options: Options(method: error.request.method),
        );
    }
    _repeatCounter = 0;
    return error;
}, onResponse: (response) async {
    if (response.statusCode == 200) {
        _repeatCounter = 0;
    }
}));

@wendux
Copy link
Contributor

wendux commented Aug 7, 2020

Please check out:
https://github.com/flutterchina/dio/blob/master/example/interceptor_lock.dart
https://github.com/flutterchina/dio/blob/master/dio/test/interceptor_test.dart

@wendux wendux reopened this Aug 7, 2020
@wendux wendux added the pinned label Aug 7, 2020
@kadnan0900
Copy link

Any Update?

@douglasiacovelli
Copy link

douglasiacovelli commented Jan 4, 2021

I've just run into a similar issue and followed the code that was used on the test 'Interceptor error lock: test' found here: https://github.com/flutterchina/dio/blob/master/dio/test/interceptor_test.dart.

It worked flawlessly except from the fact that you must keep track of how many refreshes you have already tried. This is important so you don't get in a loop and instead just logout the user. This can be done by using a request header with a retry_count, for example.

//When retrying the original request, you can add this header
request.headers['retry_count'] = 1;

//and on the error before refreshing the accesstoken you can verify the retry_count.
if (request.headers['retry_count'] == 1) {
    //logout user
    return e;
} else {
    //refresh token
}

@mdasifcse
Copy link

Same problem, refresh code enter into loop of requests
you got the answer ,plz tell how to solve this

@douglasiacovelli
Copy link

I've created a gist with the retry-count example. This code is based on this test I mentioned earlier.

https://gist.github.com/douglasiacovelli/8fe98ec4a98e72bef5da1189a19fb3b9

@mdasifcse
Copy link

mdasifcse commented Jan 13, 2021 via email

@HasikaDilshani
Copy link

I am also getting infinite loop
any solutions?

@mauriziopinotti
Copy link

mauriziopinotti commented Mar 10, 2021

after upgrading to 4.x this no longer works, I get

error: The argument type 'RequestOptions' can't be assigned to the parameter type 'Options'.

Anyone else faced this?

Of course, you can always do:

          RequestOptions options = e.response.request;

          // ...

          return _dio.request(options.path,
              options: Options(
                method: options.method,
                sendTimeout: options.sendTimeout,
                receiveTimeout: options.receiveTimeout,
                extra: options.extra,
                headers: options.headers,
                responseType: options.responseType,
                contentType: options.contentType,
                validateStatus: options.validateStatus,
                receiveDataWhenStatusError: options.receiveDataWhenStatusError,
                followRedirects: options.followRedirects,
                maxRedirects: options.maxRedirects,
                requestEncoder: options.requestEncoder,
                responseDecoder: options.responseDecoder,
                listFormat: options.listFormat,
              ));

@scopendo
Copy link

Following updating to version 4.x, I found that I needed to change my onError handler from:

Future onError(DioError err) async {
  ...
  return retryDio.request(...);
}

To the new handler signature and approach:

Future onError(DioError err, ErrorInterceptorHandler handler) async {
  ...
  try {
    final response = await retryDio.request(...);
    return handler.resolve(response);
  } on DioError catch (e) {
    return handler.reject(e);
  }
}

@vimalmistry
Copy link

final response = await retryDio.request(...);
    return handler.resolve(response);

Can you share details code?
Screenshot 2021-04-17 at 1 50 20 PM

In my case Shows error : Options? is not subtype of RequestOptions (errors.requestOptions)

@mauriziopinotti
Copy link

@vimalmistry I have shared a code snippet to handle 401 errors here: #1088 (comment)

@mounirmesselmeni
Copy link

@vimalmistry from @mauriziopinotti solution, you just have to change

return dio.request(error.requestOptions.path, options: error.requestOptions

to

return dio.fetch(options);

This will take care of passing all details about the failed request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests