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

Requested authentication state persistence isn't retained #8545

Open
dsl101 opened this issue Sep 30, 2024 · 10 comments
Open

Requested authentication state persistence isn't retained #8545

dsl101 opened this issue Sep 30, 2024 · 10 comments

Comments

@dsl101
Copy link

dsl101 commented Sep 30, 2024

Operating System

windows

Environment (if applicable)

Chrome 131, Firefox 130

Firebase SDK Version

10.13.2

Firebase SDK Product(s)

Auth

Project Tooling

React app / Vue & Quasar app

Detailed Problem Description

This page states that calls to setPersistence() prior to signInWithRedirect() should reapply the requested persistence model at the end of the redirect flow. We are seeing that despite requesting browserSessionPersistence before sending the link, and pasting the emailed link directly into the same browser tab, the persistence model is reverting to LOCAL.

This also happens with federated auth providers using signInWithRedirect().

Steps and code to reproduce issue

E.g. minimal piece of code to send a signin link by email:

  const signIn = (data) => {
    const options = {
      url: window.location.href,
      handleCodeInApp: true,     
    }
    console.log('options:', options)
    return setPersistence(auth, browserSessionPersistence).then(() => {
      console.log('auth:', auth)
      console.log('Persistence:', auth.persistenceManager.persistence)
      sendSignInLinkToEmail(auth, data.email, options).then(() => {
        console.log('sent link')
      })
    });
  };

Note the persistence printed to console here is:

useAuth.js:23 Persistence: BrowserSessionPersistence {type: 'SESSION', storageRetriever: ƒ}storageRetriever: () => window.sessionStoragetype: "SESSION"storage: (...)[[Prototype]]: BrowserPersistenceClass

After pasting the redirect link and re-entering the email address on the test app, this code detects & applies the authentication:

      if (isSignInWithEmailLink(auth, window.location.href)) {
        await signInWithEmailLink(auth, email, window.location.href)
        window.location.replace("/")  // Dismiss redirect
      } else {
        await signIn({ email });
      }

and the onAuthStateChanged() handler:

    return onAuthStateChanged(auth, async (userData) => {
      console.log("onAuthStateChanged:", userData);
      if (userData) {
        try {
          console.log("Persistence:", userData.auth.persistenceManager.persistence)
          setUser(() => userData);
        } catch (e) {
          // eslint-disable-next-line
          console.log(e);
        }
      } else {
        setUser(null);
      }

logs:

FirebaseAuthProvider.jsx:17 Persistence: IndexedDBLocalPersistence {type: 'LOCAL', _shouldAllowMigration: true, listeners: {…}, localCache: {…}, pollTimer: 1, …}
@dsl101 dsl101 added new A new issue that hasn't be categoirzed as question, bug or feature request question labels Sep 30, 2024
@google-oss-bot
Copy link
Contributor

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

@jbalidiong jbalidiong added api: auth needs-attention and removed needs-triage new A new issue that hasn't be categoirzed as question, bug or feature request labels Sep 30, 2024
@dlarocque
Copy link
Contributor

Hi @dsl101, thanks for reporting this issue.

You mentioned that this issue also occurs when using signInWithRedirect- coud you share a snippet demonstrating how you used signInWithRedirect in this case?

As a temporary workaround while we look into this, would reapplying setPersistence(auth, browserSessionPersistence) after the redirect solve this issue?

@dsl101
Copy link
Author

dsl101 commented Oct 7, 2024

Calling setPersistence() after signInWithEmailLink() partially fixes the issue:

      if (isSignInWithEmailLink(auth, window.location.href)) {
        await signInWithEmailLink(auth, email, window.location.href)
        setPersistence(auth, browserSessionPersistence)
        window.location.replace("/")  // Dismiss redirect
      } else {
        await signIn({ email });
      }

Now, after sending the link and signing in on tab 1, opening a second tab and pasting in the URL does not show me as logged in on that second tab. Indeed, I can authenticate using a second email address on the second tab, and the 2 are isolated.

However, the react app has this section in App.js:

  if (user) {
    return (
      <div>
        <div>Logged in {user.email}</div>

        <br />

        <button onClick={() => window.open("/openedTab", "_blank")}>
          Open new tab same domain
        </button>
        <br />

        <button onClick={signOut}>Logout</button>
      </div>
    );
  }

Using that button on tab 1 (authenticated) opens a new tab and it is authenticated. Is this the expected behaviour when opening a tab via window.open()?

Regarding using signInWithRedirect(), the flow is almost identical, apart from not needing the detect the sign in URL on return to the application. The button is just <button onClick={() => signInRedirect() }>Login with google</button>, and the associated function:

import {
  browserSessionPersistence,
  setPersistence,
  signInWithRedirect,
  GoogleAuthProvider,
} from "firebase/auth"

  const signInRedirect = () => {
    return setPersistence(auth, browserSessionPersistence).then(() => {
      console.log('Persistence:', auth.persistenceManager.persistence)
      return signInWithRedirect(auth, new GoogleAuthProvider()).then(() => {
        console.log('redirect succeeded')
      })
    })
  }

Again, the logged persistence SESSION before the call to signInWithRedirect(), and LOCAL after returning.

Note that redirect succeeded is never logged, so I don't believe the promise is ever resolved back to the client code, I guess due to the redirect. So I'm not sure how the workaround can be used in this case, as it's not obvious how to detect the first auth flow vs just a refresh of a long-authenticated session.

However, and I don't know if this is a client-side bug or a browser setting, but the above flow for auth works fine (albeit with the persistence issue) on Firefox, but not on Chrome. The redirect for google authentication happens, and the app is reloaded at the end of that flow, but the onAuthStateChanged() handler always shows null, not the authenticated user. This is the react code for that:

  useEffect(() => {
    return onAuthStateChanged(auth, async (userData) => {
      console.log("onAuthStateChanged:", userData);
      if (userData) {
        try {
          console.log("Persistence:", userData.auth.persistenceManager.persistence)
          setUser(() => userData);
        } catch (e) {
          // eslint-disable-next-line
          console.log(e);
        }
      } else {
        setUser(null);
      }

      setFirstCheck(() => false);
    });
  }, []);

And the console logs for Chrome:
image

and Firefox:
image

@DellaBitta
Copy link
Contributor

Hi @dsl101,

It may be the case that this is a timing issue and the persistence config is landing too late for it to properly take effect. Could you try initializing Auth with the Session persistence setting to see what the behavior is? If, even after that, there continues to be a problem then I'll bring it directly to the Auth team. Thanks!

@dsl101
Copy link
Author

dsl101 commented Oct 11, 2024

I tried initialising the app like this:

import { initializeApp } from 'firebase/app'
import { browserSessionPersistence } from 'firebase/auth'

initializeApp({
      persistence: [ browserSessionPersistence ],
      apiKey: "...",
      authDomain: "...",
      projectId: "...",
})

const auth = getAuth(app)
console.log('persistenceManager:', auth.persistenceManager)

and this logs persistenceManager: undefined, so I guess it doesn't get initialised immediately. So I added this to the onAuthStateChanged() handler:

    return onAuthStateChanged(auth, async userData => {
      console.log("[onAuthStateChanged] userData:", userData);
      console.log('[onAuthStateChanged] auth.persistenceManager.persistence.type:', auth.persistenceManager.persistence.type)

and that logs:

[onAuthStateChanged] userData: null
[onAuthStateChanged] auth.persistenceManager.persistence.type: LOCAL

before login. So, it really doesn't look like the persistence manager is behaving well at all...

@DellaBitta
Copy link
Contributor

Hi @dsl101,

Ah yes, sorry. The persisitence field isn't a member of the FirebaseOptions type passed to initializeApp.

Instead, could you invoke initailizeAuth instead of getAuth, and pass it the persistence configuration. The linked API documentation for initializeAuth has an example.

Thanks!

@dsl101
Copy link
Author

dsl101 commented Oct 12, 2024

Sorry, I read initializeAuth as initializeApp in the docs :).

Using this:

const auth = initializeAuth(app, {
  persistence: [ browserSessionPersistence ],
})

shows SESSION as the persistence type before and after authentication. I still see the problem of opening a new tab from the app via window.open() as bringing the auth state across, whereas copying and pasting the url into a new tab does not.

But in any case, that does make offering the user the choice (e.g. a 'Remember me' checkbox) difficult if it has to be done at initialisation. How can the persistence be set prior to authentication? And is the documentation that says the persistence state will be remembered wrong?

@DellaBitta
Copy link
Contributor

Hi @dsl101,

Please note that your code uses [ ] in the persistence field, but the docs do not:

const auth = initializeAuth(app, {
  persistence: browserSessionPersistence,
  popupRedirectResolver: undefined,
});

In your case, you do not need to define a popupRedirectResolver field.

@dsl101
Copy link
Author

dsl101 commented Oct 18, 2024

The docs do.

persistence can be specified as a singleton or an array, but that's beside the point. Specifying the persistence model at initialisation works, but then doesn't give the option of allowing the user the choice, and does not follow what the documentation claims (i.e. remembering the setting after redirect auth flow).

Are we agreed this is a bug of some kind? Whether it's an actual bug or just a timing issue, it ought to be possible to specify the persistence based on user input before processing the authentication.

As to why window.open() is bringing SESSION authentication state to the new tab, if this is documented behaviour somewhere, it would be good to know.

@DellaBitta
Copy link
Contributor

Ok, thanks for following up. I'll bring this up with the Firebase Auth team.

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

No branches or pull requests

5 participants