nylo-core / nylo

Nylo is the fastest way to build your next Flutter mobile app. Streamline your projects with Nylo's opinionated approach to building Flutter apps. Develop your next idea ⚡️
https://nylo.dev
MIT License
597 stars 61 forks source link

refreshToken function isn't invoked #107

Closed KhaledAlMana closed 9 months ago

KhaledAlMana commented 9 months ago

First of all, Thank you @agordn52 for this great framework.

I'm following the docs, and what has been mentioned in the issues/discussions.

refreshToken function isn't being invoked at all.

  @override
  refreshToken(Dio dio) async {
    AuthTokens? tokens = await StorageKey.userToken.read<AuthTokens>();
    if (tokens?.refreshToken == null) {
      await Auth.logout();
      routeToInitial(navigationType: NavigationType.pushAndForgetAll);
      return;
    }
    dynamic response = (await dio.post(baseUrl + "/auth/refresh",
            options: Options(
                headers: {"Authorization": "Bearer ${tokens?.refreshToken}"})))
        .data();
    //  Save the new token
    await StorageKey.userToken.store(response);
  }

Noting that the approach was mentioned, I have to retry manually.

Did I miss anything?

If we refresh on request instead, would it be better? but what if I run parallel requests?


Update [29-01-2024]

This is the interceptor approach.

class BearerAuthInterceptor extends Interceptor {
  @override
  void onRequest(
      RequestOptions options, RequestInterceptorHandler handler) async {
    if (!options.path.contains("auth/refresh")) {
      await api<ApiService>((request) => request.refreshTokens());
    }
    AuthTokens? tokens = await StorageKey.userToken.read<AuthTokens>();
    String? token = options.path.contains("auth/refresh")
        ? tokens?.refreshToken
        : tokens?.accessToken;

    if (token != null) {
      options.headers.addAll({"Authorization": "Bearer $token"});
    }
    return super.onRequest(options, handler);
  }

the refreshTokens function checks if a refresh token is needed then proceed. if condition to eliminate the recursion.

I had to skip the previous approach, but I still do not know how reactive this approach is. Especially with parallel requests scenario.

agordn52 commented 9 months ago

Hi @KhaledAlMana,

Hope you are well. Thanks for your comments about the framework.

Re. the refreshToken method Have you implemented the shouldRefreshToken? This will tell Nylo when when to refresh the token.

E.g.

@override
Future<bool> shouldRefreshToken() async {
    User? user = Auth.user();

    if (user.token.expiryDate.isPast()) {
        // Check if the token is expired
        // This will trigger the [refreshToken] method
        return true;
    }
    return false;
}

The full docs are here, hope that helps :)

KhaledAlMana commented 9 months ago

Hello @agordn52,

Yes, and during the debug, it is being invoked. However, refreshToken isn't invoked and all I have no clue why.


  @override
  Future<RequestHeaders> setAuthHeaders(RequestHeaders headers) async {
    AuthTokens? tokens = await StorageKey.userToken.read<AuthTokens>();
    if (tokens?.accessToken != null) {
      headers.addBearerToken(tokens?.accessToken ?? "");
    }
    return headers;
  }

  @override
  Future<bool> shouldRefreshTokens() async {
    try {
      AuthTokens? tokens = await StorageKey.userToken.read<AuthTokens>();
      if (tokens?.accessToken == null) {
        return false;
      }
      // If the token is expired, where expiredAt is epoch time
      // add 15 seconds to the expiry time to account for latency
      if (tokens?.expiresAt != null &&
          DateTime.now().isAfter(
              tokens?.expiresAt!.subtract(Duration(seconds: 15)) ??
                  DateTime.now())) {
        return true;
      } else {
        return false;
      }
    } catch (e) {
      print(e);
      return false;
    }
  }

  @override
  refreshToken(Dio dio) async {
    // debug is not hitting this function, nor printing the below.
    print("refreshing token");
    AuthTokens? tokens = await StorageKey.userToken.read<AuthTokens>();
    print(tokens?.refreshToken);
    if (tokens?.refreshToken == null) {
      await Auth.logout();
      routeToInitial(navigationType: NavigationType.pushAndForgetAll);
      return;
    }
    dynamic response = (await dio.post(baseUrl + "/auth/refresh",
            options: Options(
                headers: {"Authorization": "Bearer ${tokens?.refreshToken}"})))
        .data();
    //  Save the new token
    await StorageKey.userToken.store(response);
  }
agordn52 commented 9 months ago

Hi @KhaledAlMana,

Thanks for sharing your code. I see a few things wrong that should be changed, please try the below 👍

@override
Future<RequestHeaders> setAuthHeaders(RequestHeaders headers) async {
    AuthTokens? tokens = await StorageKey.userToken.read<AuthTokens>();
    if (tokens?.accessToken != null) {
      headers.addBearerToken(tokens?.accessToken ?? "");
    }
    return headers;
}

@override
Future<bool> shouldRefreshTokens() async {
  AuthTokens? tokens = await StorageKey.userToken.read<AuthTokens>();
  if (tokens?.accessToken == null) {
    return true; // if accessToken is null then you should want to get a new 'AuthTokens'
   }
   // If the token is expired, where expiredAt is epoch time
  // add 15 seconds to the expiry time to account for latency
  if (DateTime.now().isAfter(tokens?.expiresAt?.subtract(Duration(seconds: 15)) ?? DateTime.now())) {
        return true;
  }
  return false;
}

@override
refreshToken(Dio dio) async {
    // debug is not hitting this function, nor printing the below.
    print("refreshing token");
    AuthTokens? tokens = await StorageKey.userToken.read<AuthTokens>();
    print(tokens?.refreshToken);
    if (tokens?.refreshToken == null) {
      await Auth.logout();
      routeToInitial(navigationType: NavigationType.pushAndForgetAll);
      return;
    }
    dynamic response = (await dio.post(baseUrl + "/auth/refresh",
            options: Options(
                headers: {"Authorization": "Bearer ${tokens?.refreshToken}"})))
        .data; // data() not a function, use data instead
    //  Save the new token
    await StorageKey.userToken.store(response);
}
agordn52 commented 9 months ago

I'm going to close this as resolved.

If you need help understanding networking, the docs on nylo.dev are up-to-date so you'll be able to learn more 👍

KhaledAlMana commented 9 months ago

Sorry, I have been busy lately.

I got the .data; issue but the problem is that the function refreshToken is still not being called. After the latest update, I tested it again, it works fine.

Thank you @agordn52 for your support.