auth-js icon indicating copy to clipboard operation
auth-js copied to clipboard

Support for multiple tabs does NOT respect the 'persistSession' option across tabs

Open chipilov opened this issue 3 years ago • 2 comments

Bug report

Describe the bug

Supabase's GoTrue JavaScript client has explicit support for multiple tabs which is enabled by default.

Supabase's GoTrue JavaScript client also has support for the commonly-used 'Remember Me' feature via the persistSession parameter of the SupabaseAuthClient constructor.

The issue is that if a user has 2 tabs open - one where the auth client has the persistSession set to false and one where the parameter is set to true - the different tabs will continue to behave differently in terms of persisting the session EVEN though logging from one tab will automatically log-in the other tab.

To me, it seems that it's more logical that once a user logs-in via tab A, all other tabs that get automatically logged-in will inherit the persistSession behavior of the tab A.

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Open 2 tabs with a Supabase-auth enabled application - one where persistSession is set to true (let's call this tab A) and one where persistSession is set to false (let's call this tab B)
  2. Sign-in from tab A - notice that tab B detects the sign-in from tab A and now the content in tab B shows info relevant to the user that signed-in via tab A
  3. Close tab A
  4. Continue to work with tab B until the JWT is refreshed at least once
  5. Close tab B and re-open the application in tab C

Expected behavior

The client is still signed-in because when they signed-in originally via tab A, they chose to be remembered (i.e. the persistSession parameter was true);

Actual behavior

The client is no longer signed-in and needs to sign-in again.

Consequences

This behavior can lead to failed token refresh requests because of the re-use of an invalidated refresh token.

This can happen when tab A (as defined above) is closed and its token remains in localStorage while tab B continues to send token refresh requests without updating the token in localStorage. Hence, localStorage will be left with a stale refresh token which will be incorrectly re-used in at least one of 2 cases:

  • If the user opens a new tab C with the app and the app initializes a new SupabaseAuthClient object, this new object will try to use the refresh token in local storage to recover the session with the stale refresh token (see https://github.com/supabase/gotrue-js/blob/30fb5729a1988dbd7f61d58ec10eb81de63f93c7/src/GoTrueClient.ts#L107)
  • If the user minimizes tab B and then puts it back into focus at just the right time, it will also try to refresh and recover using the token from local storage (this behavior was added with https://github.com/supabase/gotrue-js/pull/278/, see https://github.com/supabase/gotrue-js/blob/30fb5729a1988dbd7f61d58ec10eb81de63f93c7/src/GoTrueClient.ts#L799)

The problem is made worse for users of the library because the library does NOT expose the persistSession config - this means that users of the library can only work around this by inspecting the data in localStorage for which there is also no publicly defined API as far as I can tell.

System information

  • Version of supabase-js: 1.35.3
  • Version of gotrue-js: 1.22.15

chipilov avatar Jun 06 '22 14:06 chipilov

Hey @chipilov, could you please clarify how your application managed to get into a state where the persistSession value on both tabs are different?

Thanks for taking the time to do such a detailed write-up of the issue btw!

kangmingtay avatar Jun 07 '22 18:06 kangmingtay

Hi @kangmingtay,

In my app, I set the persistSession parameter to false by default (I do this because I prefer the user's explicit approval before I start storing any tokens in the browser). With this setup it's very easy to end up with multiple tabs that have a different value for persistSession, for example:

  1. User opens tab A, loads the app, clicks on the 'Remember Me' checkbox and signs-in. From this point on, tab A has persistSession set to true;
  2. Later, the user opens another tab B and loads the app again (maybe they forgot they already have the app in tab A OR they want another tab so they can look at different things at the same time). As soon as the app is loaded, the user is signed-in (since the constructor of the auth client calls _recoverAndRefresh()), however, the persistSession is actually set to false because that is the default for my app;

NOTE, that you can also get in the same situation even if the default for persistSession is true. Here is an example:

  1. User opens tab A, loads the app and explicitly un-checks the 'Remember Me' checkbox. From this point on, tab A has persistSession set to false;
  2. Later, the user opens another tab B and loads the app again. This time, they are NOT signed-in automatically because there is no token in localStorage to recover from, so they sign-in again but this time they leave the 'Remember Me' checkbox checked, so persistSession is true for tab B. Now they are logged-in in both tabs A and B and the tabs have different values for persistSession.

Let me know if anything is unclear.

chipilov avatar Jun 08 '22 07:06 chipilov

This is behavior that is not something the library can provide. You should synchronize the "remember me" state across tabs manually. persistSession is really a low-level flag that you can use to build complex scenarios like this.

We may consider having some explicit form of "remember me" implemented in the next major version, but that is far away.

hf avatar Dec 30 '22 18:12 hf