supabase / supabase-flutter

Flutter integration for Supabase. This package makes it simple for developers to build secure and scalable products.
https://supabase.com/
MIT License
662 stars 155 forks source link

Supabase throwing JWT expired. PostgrestException(message: JWT expired, code: PGRST301, details: Unauthorized, hint: null) #827

Closed Aadim1 closed 2 months ago

Aadim1 commented 4 months ago

A couple of hours ago, I started having problem I never had before. The supabase upon calling the api started throwing. PostgrestException(message: JWT expired, code: PGRST301, details: Unauthorized, hint: null). My JWT expiration time was 3600 seconds. I got it down to 10 seconds, to see if the issue persists it did. I played around with from range 10 to 1000 to 3600, all of them started throwing JWT expired, after some time. I have awaited supabase at main(), and I also didn't have the authOptions. I also changed the authOptions to autoRefreshToken.

final sup = await Supabase.initialize(
    url: 'URL',
    anonKey:
        'ANON_KEY',
    authOptions: const FlutterAuthClientOptions(
      autoRefreshToken: true,
    ),
  );

I then thought maybe I was being rate limited, but I forced the session to refresh every 1 seconds with supabase.auth.refreshSession(); and got a rate limit error AuthException(message: Rate limit exceeded, statusCode: 429). This was working fine around 3 hours ago when I was last testing it.

I am using supabase for auth, db, and realtime.


try {
    final appointments = await supabase
        .from("<table>")
        .select('''
            ...
          ''')
    log('Appointment: $appointments ${lastAppointmentRefresh?.toUtc().toIso8601String()}');
    return appointments;
  } catch (e) {
    if (e is PostgrestException && e.code == "PGRST301" && retries > 0) {
      log("JWT EXpired...");
      await supabase.auth.refreshSession();
      return await fetchAllAppointmentNetwork(
          lastAppointmentRefresh, businessId, locationId,
          retries: retries - 1);
    }

    log('Fetch Appointment Error: $e');
    return null;
  }

When I catch the error, and manually refresh the token, everything works fine in the first try.

I don't know if it matters but this is how I am using auth stream


supabaseAuthStream?.cancel();
    supabaseAuthStream =
        supabase.auth.onAuthStateChange.listen((authState) async {
      final session = authState.session;
      final event = authState.event;
      if (session == null) {
        changeLoggedInState(false);
      } else {

        if (some_prefs_condition) {
          changeLoggedInState(false);
          return;
        }

        if (authState.event == AuthChangeEvent.tokenRefreshed) {
          supabase.auth.refreshSession();
        }

        changeLoggedInState(true);
      }
    });

    supabaseAuthStream?.onError((e) {
      log("Supabase Error: $e");
    });

I never get the JWT is expired in onError either.

I am using supabase supabase_flutter: ^2.3.2

dshukertjr commented 4 months ago

Is the app available on a public GitHub repo? If not, is it possible to extract the part of the app that is causing the issue and put it in a public repo? We have been seeing these issues pop up, and I would love to see what exactly is causing it.

Aadim1 commented 4 months ago

Hey, I can't make the whole app public, but I did try to replicate parts of the app that used supabase, and had no luck having the same issue. In my main app, the issue also became less frequent.

We basically saw that if we manually refresh the session, in a set interval(around the same time as our expiration), it mitigates the issue for like 90% of the time. For an off chance, the issue still persists, we came up with a generic funciton, to wrap our calls to supabase backend.

Future<T?> retryOnPostgrestException<T>(
  Future<T?> Function() function,
  int maxRetries, {
  Duration delay = const Duration(seconds: 0),
  void Function(PostgrestException)? onError,
}) async {
  int retryCount = 0;
  while (retryCount < maxRetries) {
    try {
      return await function();
    } on PostgrestException catch (error) {
      if (onError != null) {
        onError(error);
      }
      retryCount++;
      print('Caught PostgrestException, retrying in $delay...');
      await Future.delayed(delay);
    } catch (e) {
      return null;
    }
  }
  return null;
}
dshukertjr commented 4 months ago

but I did try to replicate parts of the app that used supabase, and had no luck having the same issue.

That is unfortunate to hear. If you notice any other patterns on when it happens, please do not hesitate to let us know.

elliottetzkorn commented 4 months ago

I am having the same issue - as of one week ago my app is failing now with JWT expiration refresh. I am not comfortable making it public but I am happy to add you as a collaborator @dshukertjr so that you can look at the code. Does that sound good?

dshukertjr commented 4 months ago

@ellmaki That would be amazing!

elliottetzkorn commented 4 months ago

@dshukertjr I have sent an invite. If you can provide any insight at all into why the JWT bug might be popping up here I would appreciate it! I am most easily reachable on Discord my name is @soupasaservice.

dshukertjr commented 4 months ago

@ellmaki sounds good. I'm away from my laptop at the moment, but will take a look as soon as I get back.

Aadim1 commented 4 months ago

Hey while I was looking for the root cause, i stumbled upon this.

  void _setTokenRefreshTimer(
    Duration timerDuration, {
    String? refreshToken,
    String? accessToken,
  }) {
    _refreshTokenTimer?.cancel();
    _refreshTokenRetryCount++;
    if (_refreshTokenRetryCount < Constants.maxRetryCount) {
      developer.log("Refresh token timer set for duration ${timerDuration.inSeconds}");
      _refreshTokenTimer = Timer(timerDuration, () async {
        developer.log("Timer called....");
        try {
          developer.log('\x1B[31mToken was refreshed here...\x1B[0m');
          await _callRefreshToken(
            refreshToken: refreshToken,
            accessToken: accessToken,
            ignorePendingRequest: true,
          );
        } catch (_) {
          developer.log("Error while refreshing token....");
          // Catch any error, because in this case they should be handled by listening to [onAuthStateChange]
        }
      });
    } else {
      throw AuthException('Access token refresh retry limit exceeded.');
    }
  }

Pretty self-explanatory function, but while after the first session was created. developer.log("Refresh token timer set for duration ${timerDuration.inSeconds}"); was called and logged Refresh token timer set for duration: 33152. The refresh duration was set to 30 seconds for debugging.

elliottetzkorn commented 4 months ago

Any progress on this it's really messing my app up

govi218 commented 4 months ago

I'm noticing this in react native (with expo) as well

dshukertjr commented 4 months ago

We are currently not able to reliably reproduce this issue. If you can reproduce this issue consistently, please let us know.

Also, any information regarding this issue is helpful. Things like

One thing that could cause this issue for sure is if the clock on the user's device was off by more than a minute. I doubt that that is the main cause of this issue, but if you feel like the clock being off was the cause for you and your app, then let me know.

dshukertjr commented 4 months ago

@Aadim1 Are you able to pring Refresh token timer set for duration: 33152 on the debug console consistently, or did you see it once, and can no longer reproduce it?

pfvbell commented 4 months ago

Hi, I am also getting this issue with my app.

I'm using the supabase python sdk.

Code looks like this:

assignments = supabase.table('assignments').select('title, due_date, assignment_id, question_type, marks_check, feedback_check', 'creation_date').eq('teacher_email', email).execute()

I thought it might be related to the new python sdk version update a few days ago but I'm not sure.

dshukertjr commented 2 months ago

We have shipped an update that will hopefully make things better with this issue. If you could try out v2.5.0 of supabase_flutter and see if things are better, that would be great.

elliottetzkorn commented 2 months ago

Great, updating now thank you

dshukertjr commented 2 months ago

Let me close this issue for now, but if the same issue persists, please feel free to reopen again.