Using Interceptor in Dio for Flutter to Refresh Token

为君一笑 提交于 2020-08-21 05:21:12

问题


I am trying to use Interceptor with Dio in flutter, I have to handle Token expire. following is my code

Future<Dio> getApiClient() async {
    token = await storage.read(key: USER_TOKEN);
    _dio.interceptors.clear();
    _dio.interceptors
        .add(InterceptorsWrapper(onRequest: (RequestOptions options) {
      // Do something before request is sent
      options.headers["Authorization"] = "Bearer " + token;
      return options;
    },onResponse:(Response response) {
        // Do something with response data
        return response; // continue
    }, onError: (DioError error) async {
      // Do something with response error
      if (error.response?.statusCode == 403) {
        // update token and repeat
        // Lock to block the incoming request until the token updated

        _dio.interceptors.requestLock.lock();
        _dio.interceptors.responseLock.lock();
        RequestOptions options = error.response.request;
        FirebaseUser user = await FirebaseAuth.instance.currentUser();
        token = await user.getIdToken(refresh: true);
        await writeAuthKey(token);
        options.headers["Authorization"] = "Bearer " + token;

        _dio.interceptors.requestLock.unlock();
        _dio.interceptors.responseLock.unlock();
        _dio.request(options.path, options: options);
      } else {
        return error;
      }
    }));
    _dio.options.baseUrl = baseUrl;
    return _dio;
  }

problem is instead of repeating the network call with the new token, Dio is returning the error object to the calling method, which in turn is rendering the wrong widget, any leads on how to handle token refresh with dio?


回答1:


I solved it using interceptors in following way :-

  Future<Dio> getApiClient() async {
    token = await storage.read(key: USER_TOKEN);
    _dio.interceptors.clear();
    _dio.interceptors
        .add(InterceptorsWrapper(onRequest: (RequestOptions options) {
      // Do something before request is sent
      options.headers["Authorization"] = "Bearer " + token;
      return options;
    },onResponse:(Response response) {
        // Do something with response data
        return response; // continue
    }, onError: (DioError error) async {
      // Do something with response error
      if (error.response?.statusCode == 403) {
        _dio.interceptors.requestLock.lock();
        _dio.interceptors.responseLock.lock();
        RequestOptions options = error.response.request;
        FirebaseUser user = await FirebaseAuth.instance.currentUser();
        token = await user.getIdToken(refresh: true);
        await writeAuthKey(token);
        options.headers["Authorization"] = "Bearer " + token;

        _dio.interceptors.requestLock.unlock();
        _dio.interceptors.responseLock.unlock();
        return _dio.request(options.path,options: options);
      } else {
        return error;
      }
    }));
    _dio.options.baseUrl = baseUrl;
    return _dio;
  }



回答2:


You would get a response status code as 401 for token expiration. In order to request new access token, you need to use post method along with form data and required Dio's options (content-type and headers). Below is the code shows how to request new token.

After successful request, if you get the response status code as 200, then you will get new access token value along with refresh token value and save them in any storage you prefer to use. For example, Shared preferences.

Once you have new access token saved, you can use it to fetch data using get method shown in the same code below.

onError(DioError error) async {
    if (error.response?.statusCode == 401) {
      Response response;
      var authToken = base64
          .encode(utf8.encode("username_value" + ":" + "password_value"));
      FormData formData = new FormData.from(
          {"grant_type": "refresh_token", "refresh_token": refresh_token_value});
      response = await dio.post(
        url,
        data: formData,
        options: new Options(
            contentType: ContentType.parse("application/x-www-form-urlencoded"),
            headers: {HttpHeaders.authorizationHeader: 'Basic $authToken'}),
      );
      if (response.statusCode == 200) {
        response = await dio.get(
          url,
          options: new Options(headers: {
            HttpHeaders.authorizationHeader: 'Bearer access_token_value'
          }),
        );
        return response;
      } else {
        print(response.data);
        return null;
      }
    }
    return error;
  }



回答3:


I not finished the Custom Http Client implementation yet but it can help you.

Custom Http

Interceptor

Main




回答4:


I have found a simple solution that looks like the following:

this.api = Dio();

    this.api.interceptors.add(InterceptorsWrapper(
       onError: (error) async {
          if (error.response?.statusCode == 403 ||
              error.response?.statusCode == 401) {
              await refreshToken();
              return _retry(error.request);
            }
            return error.response;
        }));

Basically what is going on is it checks to see if the error is a 401 or 403, which are common auth errors, and if so, it will refresh the token and retry the response. My implementation of refreshToken() looks like the following, but this may vary based on your api:

Future<void> refreshToken() async {
    final refreshToken = await this._storage.read(key: 'refreshToken');
    final response =
        await this.api.post('/users/refresh', data: {'token': refreshToken});

    if (response.statusCode == 200) {
      this.accessToken = response.data['accessToken'];
    }
  }

I use Flutter Sercure Storage to store the accessToken. My retry method looks like the following:

  Future<Response<dynamic>> _retry(RequestOptions requestOptions) async {
    final options = new Options(
      method: requestOptions.method,
      headers: requestOptions.headers,
    );
    return this.api.request<dynamic>(requestOptions.path,
        data: requestOptions.data,
        queryParameters: requestOptions.queryParameters,
        options: options);
  }

If you want to easily allows add the access_token to the request I suggest adding the following function when you declare your dio router with the onError callback:

onRequest: (options) async {
          options.headers['Authorization'] = 'Bearer: $accessToken';
          return options;
        },


来源:https://stackoverflow.com/questions/56740793/using-interceptor-in-dio-for-flutter-to-refresh-token

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!