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

feat(gotrue, supabase_flutter): New auth token refresh algorithm #879

Merged
merged 25 commits into from
Apr 9, 2024

Conversation

dshukertjr
Copy link
Member

@dshukertjr dshukertjr commented Apr 2, 2024

What kind of change does this PR introduce?

Change the way we refresh the auth tokens. It also creates two new auth methods startAutoRefresh() and stopAutoRefresh() to start and stop the token refresh process.

What is the current behavior?

We have a relatively complex algorithm to refresh the auth tokens. It can be hard to debug what is happening through out the token refresh flow.

What is the new behavior?

The flow is simplified, and easier to understand.

  1. Every 10 seconds the session is checked if it's expired. Call this "tick"
  2. If expired, request a new token to the auth server
  • If the request fails due to a network issue, it retries with exponential backoff (i.e. 200ms, 400ms, 800ms ...)
  • Any additional token refresh attempt will also be bundled into the same Completer
  • The retry keeps going until the next tick
  1. The completer is released (either succeded and returns the token or throws an exception) before the next tick.

In addition to the algorithm update, this PR has introduced the start and stop auto refresh methods, and uses them with the lifecycle events of Flutter. Specifically, when the app is in the background, it stops the SDK to attempt to refresh the token. This is because when the app is in the background, HTTP requests may or may not go through and the app could be killed any time. In order to reduce the chance of something wrong happening with the token refresh requests, the refresh requests should only be fired when the app is in the foreground.

Additional context

Closes #317
Related #171
Related #788
Related #827
Closes #829
Related #860
Closes #872

@dshukertjr dshukertjr marked this pull request as ready for review April 2, 2024 14:28
packages/gotrue/lib/src/constants.dart Outdated Show resolved Hide resolved
packages/gotrue/lib/src/gotrue_client.dart Outdated Show resolved Hide resolved
packages/gotrue/lib/src/gotrue_client.dart Outdated Show resolved Hide resolved
packages/gotrue/lib/src/gotrue_client.dart Outdated Show resolved Hide resolved
packages/gotrue/lib/src/gotrue_client.dart Outdated Show resolved Hide resolved
packages/gotrue/lib/src/gotrue_client.dart Outdated Show resolved Hide resolved
packages/gotrue/lib/src/gotrue_client.dart Outdated Show resolved Hide resolved
packages/gotrue/lib/src/types/auth_exception.dart Outdated Show resolved Hide resolved
case AppLifecycleState.detached:
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
Supabase.instance.client.auth.stopAutoRefresh();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason to stop auth refresh here? From what I read in the documentation the app may still be running in the background.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason is the following:

In addition to the algorithm update, this PR has introduced the start and stop auto refresh methods, and uses them with the lifecycle events of Flutter. Specifically, when the app is in the background, it stops the SDK to attempt to refresh the token. This is because when the app is in the background, HTTP requests may or may not go through and the app could be killed any time. In order to reduce the chance of something wrong happening with the token refresh requests, the refresh requests should only be fired when the app is in the foreground.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry, seems like I didn't read that.
But does it matter if a refresh request fails? The app could still be running, so the application could send requests to supabase with outdated access token. If the refresh fails, then requests from the application will also fail, which is fine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem at all!

The app could still be running, so the application could send requests to supabase with outdated access token. If the refresh fails, then requests from the application will also fail, which is fine.

Actually, stopAutoRefresh() only stops periodically checking the token if it's expired then refresh if expired, and if a user would to make a request after stopAutoRefresh() has been called, the auth HTTP client will still refresh the token as needed.

All it's doing is avoiding a slim chance of the auth refresh request firing, which reaches the server, but before the response comes back the connection is lost. This is where the app could be left with an expired JWT. This could very much happen when the app is in the foreground as well, but if the app is running for a long time in the background, there might be more chances of something like this happening.

It was advice from our Auth engineer and we also recommend it for React native apps. On step 4 there is pretty much the same code:
https://supabase.com/docs/guides/auth/quickstarts/react-native

Copy link
Collaborator

@Vinzent03 Vinzent03 Apr 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah I forgot that the auth http client still exists. Then you are right this is fine. Thanks for the explanation.
I will one last time go through the changes now

dshukertjr and others added 4 commits April 3, 2024 09:52
Co-authored-by: Vinzent <vinzent03@proton.me>
Co-authored-by: Vinzent <vinzent03@proton.me>
Co-authored-by: Vinzent <vinzent03@proton.me>
Co-authored-by: Vinzent <vinzent03@proton.me>
if (!_refreshTokenCompleter!.isCompleted) {
_refreshTokenCompleter!.completeError(error, stack);
}
_onAuthStateChangeController.addError(error, stack);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now missing from what I see. I think we should keep it when it's not retryable, because when this is called from a tick, the not retriable exception is nowhere reported.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! I added it back!

packages/gotrue/lib/src/gotrue_client.dart Outdated Show resolved Hide resolved
/// Starts an auto-refresh process in the background. Close to the time of expiration a process is started to
/// refresh the session. If refreshing fails it will be retried for as long as necessary.
///
/// If you set the `autoRefreshToken` to `true`, you don't need to call this function, it will be called for you.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not really true. Only when using the supabase_flutter package this method is called, nowhere else. Additionally, autoRefreshToken should be respected when calling startAutoRefresh in supabase_flutter as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! Pushed a fix.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the auto refresh should be started when only using the supabase or gotrue package as well. Not only when using the flutter package

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Let me change the code right now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Vinzent03 Updated it to call the startAutoRefresh from within gotrue.

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