supabase / supabase-js

An isomorphic Javascript client for Supabase. Query your Supabase database, subscribe to realtime events, upload and download files, browse typescript examples, invoke postgres functions via rpc, invoke supabase edge functions, query pgvector.
https://supabase.com
MIT License
2.86k stars 220 forks source link

auth.onAuthStateChanged triggers on tab switch #442

Closed noty-nick closed 2 years ago

noty-nick commented 2 years ago

Bug report

Describe the bug

auth.onAuthStateChanged triggers on tab switch

To Reproduce

  1. Create starter project using this guide https://supabase.com/docs/guides/with-vue-3

  2. add lines to src/main.js

    import { supabase } from "./supabase";
    supabase.auth.onAuthStateChange(console.log);
  3. run npm run dev

  4. open http://localhost:3000 in browser

  5. open dev console

  6. Switch to another tab

  7. Switch back to previous tab

Expected behavior

1 SIGNED_IN event in console

Actuall behaviour

2 SIGNED_IN event in console

Screenshots

https://www.loom.com/share/56993404e5f348d2bb23b68b0ad15096

System information

OrangeNote commented 2 years ago

createClient(supabaseUrl, supabaseKey, { ...otherOptions, multiTab: false }) should disable this behavior. I am not sure it's been documented (yet), but I found this option in SupabaseClientOptions and it fixed the issue for me.

noty-nick commented 2 years ago

@OrangeNote thanks, I will try that

Do you mean this is expected behaviour?

OrangeNote commented 2 years ago

I'm not sure if it's intentional or if it's an actual issue. I wonder if the object actually changes or if it's always the same after each event. If it stays the same, then I'd say it's a bug or a limitation of some sort when the multiTab option is set.

noty-nick commented 2 years ago

createClient(supabaseUrl, supabaseKey, { ...otherOptions, multiTab: false }) should disable this behavior. I am not sure it's been documented (yet), but I found this option in SupabaseClientOptions and it fixed the issue for me.

This workaround works for me, I'm closing this issue, as it looks like not a bug, but expected behaviour

sjones512 commented 1 year ago

It looks like that workaround has been removed.

Any guidance now that the multiTab option is no longer supported?

https://github.com/supabase/supabase-js/commit/39b2642d156caa412afaef788fdfa5a7f50e7e6f

ian-s-mcb commented 1 year ago

@sjones512 I noticed that that workaround was removed too, but in the release notes for v2 I found a helpful hint.

Deprecated and removed setting for multitab support because getSession() and gotrue’s reuse interval setting takes care of session management across multiple tabs (PR)

In my case, the React user-management quickstart project, I fixed the unwanted XHR to the auth backend by making the following change in src/App.js. I hope it helps you too!

// Before
useEffect(() => {
  supabase.auth.getSession().then(({ data: { session } }) => {
    setSession(session)
  })

  supabase.auth.onAuthStateChange((_event, session) => {
    setSession(session)
  })
}, [])

// After
useEffect(() => {
  supabase.auth.getSession().then(({ data: { session } }) => {
    setSession(session)
  })

  supabase.auth.onAuthStateChange((event, _session) => {
    if (EVENT === 'SIGN_OUT') {
      setSession(null)
    }
  })
}, [])
madebyfabian commented 1 year ago

Could we reopen this? I think we need another event that is being fired when we change tabs. Because SIGNED_IN, from my perspective, means that a user just has signed in, or got signed in, and not just the session refreshed due to changed tabs.

Maybe it would make sense to have a SIGNED_IN_REFRESH or something, that would be fired.

I am currently handing the SIGNED_IN so that it redirects to the home route of my app, so everytime a user changes between tabs, it'll redirect them, which is not particularly what a user would want.

rdunk commented 1 year ago

Possible workaround:

let session = null;

supabase.auth.getSession().then(async ({ data }) => {
  if (data.session) {
    session = data.session;
  }
});

supabase.auth.onAuthStateChange((event, _session) => {
  if (event === 'SIGNED_IN' && !session && _session) {
    // Only triggered on sign in
  }
  session.value = _session;
});
zakaria-chahboun commented 1 year ago

For SvelteKit users, I did this:

+layout.svelte

let session = null;
onMount(async () => {
    // get the current session
    const {
        data
    } = await supabaseClient.auth.getSession();
    session = data.session;

    // refresh: we did this when user is login with a provider (e.g google)
    if (session && $page.data.session == null) {
        await invalidateAll();
        // location.reload();
    }

    // supabase auth listener
    const {
        data: {
            subscription
        }
    } = supabaseClient.auth.onAuthStateChange((event, new_session) => {
        /*
            We did this condition cuz of this 'onAuthStateChange' function is triggered
            Whenever the user changes the browser tab!
            So - we do not do anything if the session is not changed!
        */
        if (session?.access_token != new_session?.access_token) {
            console.log('session changed');
            session = new_session;

            // reload all routes depends on supabase session
            invalidate('supabase:auth');
            if (event == 'SIGNED_IN') {
                // Do what you want here
            }
        }
    });
    return () => {
        subscription.unsubscribe();
    };
});
MEAN-James commented 10 months ago

For nuxt users using @nuxtjs/supabase. Seems to be working, thanks @rdunk!

<script lang="ts" setup>
import { useUsers } from "./store/user";
import { Subscription, Session } from "@supabase/gotrue-js";

let authStateSub: Subscription | null = null;
let session: Session | null = null;

onMounted(async () => {
  await useSupabaseClient()
    .auth.getSession()
    .then(({ data }) => {
      if (data.session) {
        session = data.session;
      }
    });

  authStateSub = useSupabaseClient().auth.onAuthStateChange(
    async (event, _session) => {
      console.log(event, session);
      if (!session && _session && event === "SIGNED_IN") {
        console.log("user logged in", _session.user);
        await useUsers().updateOnlineStatus(_session.user.id, true);
        session = _session;
      } else if (session && event === "SIGNED_OUT") {
        console.log("user logged out", session.user);
        await useUsers().updateOnlineStatus(session.user.id, false);
        session = null;
      }
    }
  ).data.subscription;
});

onUnmounted(() => {
  if (authStateSub) {
    authStateSub.unsubscribe();
  }
});
mijorus commented 7 months ago

Hi, not sure if it is related but I want to share my experience with a similar issue.

Basically, I was using the auth.onAuthStateChange callback to make a request to the DB supabase.from('table')....

This resulted in a weird behaviour that was probably causing an internal loop (my guess), as any consequent query was getting stuck.

I just removed the query from the callback

internpoon commented 6 months ago

For SvelteKit users, I did this:

+layout.svelte

let session = null;
onMount(async () => {
    // get the current session
    const {
        data
    } = await supabaseClient.auth.getSession();
    session = data.session;

    // refresh: we did this when user is login with a provider (e.g google)
    if (session && $page.data.session == null) {
        await invalidateAll();
        // location.reload();
    }

    // supabase auth listener
    const {
        data: {
            subscription
        }
    } = supabaseClient.auth.onAuthStateChange((event, new_session) => {
        /*
          We did this condition cuz of this 'onAuthStateChange' function is triggered
          Whenever the user changes the browser tab!
          So - we do not do anything if the session is not changed!
        */
        if (session?.access_token != new_session?.access_token) {
            console.log('session changed');
            session = new_session;

            // reload all routes depends on supabase session
            invalidate('supabase:auth');
            if (event == 'SIGNED_IN') {
                // Do what you want here
            }
        }
    });
    return () => {
        subscription.unsubscribe();
    };
});

Doesn't work, if I have 2 tabs opened, if i log out on 1 tab, the other tab still can browse protected route.

PranavvvSingh commented 5 months ago

Hi, not sure if it is related but I want to share my experience with a similar issue.

Basically, I was using the auth.onAuthStateChange callback to make a request to the DB supabase.from('table')....

This resulted in a weird behaviour that was probably causing an internal loop (my guess), as any consequent query was getting stuck.

I just removed the query from the callback

Thanks helped me out