gladly-team / next-firebase-auth

Simple Firebase authentication for all Next.js rendering strategies
https://nfa-example-git-v1x-gladly-team.vercel.app/
MIT License
1.34k stars 291 forks source link

Fails to work for 3rd party auth when used for staging & prod in the same session #553

Closed nmalancea closed 1 year ago

nmalancea commented 1 year ago

I get into a state where I can't log in with Google when using multiple versions of my app in different browser tabs or windows.

My scenario:

  1. Login w/ Google into staging site
  2. Open a new tab
  3. Login w/ Google into production site
  4. Return to the 1st tab
  5. Log out
  6. Click Google to login again
  7. ISSUE - returns back to the login screen after showing the normal interstitial screen trying to log in w/ Google.

The Network tab in dev tools shows a call to api/logout after step 6. Dev tools also show me the place in the code where it was called from. After un-minifying the file, it is line 15008, at the 2nd fetch below.

var V = function() {
    var e = O()((function*(e) {
        var t, {loginAPIEndpoint: n, logoutAPIEndpoint: r, onLoginRequestError: i, onLogoutRequestError: o} = I();
        if (e.id) {
            var a = yield e.getIdToken();
            if (!(t = yield fetch(n, {
                method: "POST",
                headers: {
                    Authorization: a
                },
                credentials: "include"
            })).ok) {
                var s = yield t.json()
                  , u = new Error("Received ".concat(t.status, " response from login API endpoint: ").concat(JSON.stringify(s)));
                if (!i)
                    throw u;
                yield i(u)
            }
        } else if (!(t = yield fetch(r, { // <------------------- `api/logout` called by this `fetch`
            method: "POST",
            credentials: "include"
        })).ok) {
            var c = yield t.json()
              , l = new Error("Received ".concat(t.status, " response from logout API endpoint: ").concat(JSON.stringify(c)));
            if (!o)
                throw l;
            yield o(l)
        }
        return t
    }
    ));
    return function(t) {
        return e.apply(this, arguments)
    }
}()

Does anyone know what may be causing this?

Versions

next-firebase-auth version: 0.14.3 Firebase JS SDK: 8.10.1 Next.js: 12.1.6

Debug and error logs No errors shown by onVerifyTokenError or onTokenRefreshError.

Additional context My 3rd party auth buttons:

export const ThirdPartyAuth = () => {
  const google = useMemo(() => new firebase.auth.GoogleAuthProvider(), [])
  const apple = useMemo(() => new firebase.auth.OAuthProvider('apple.com'), [])
  const facebook = useMemo(() => new firebase.auth.FacebookAuthProvider(), [])
  return (
    <div className="row">
      <div className="col-12">
        <div className={styles['c-third-party-auth']}>
          <Button
            label={<FontAwesomeIcon icon={faGoogle} size="lg" />}
            color="google"
            onClick={() => firebase.auth().signInWithRedirect(google)}
          />
          <Button
            label={<FontAwesomeIcon icon={faApple} size="lg" />}
            color="apple"
            onClick={() => firebase.auth().signInWithRedirect(apple)}
          />
          <Button
            label={<FontAwesomeIcon icon={faFacebook} size="lg" />}
            color="facebook"
            onClick={() => firebase.auth().signInWithRedirect(facebook)}
          />
        </div>
      </div>
    </div>
  )
}

The hook I call in my _app.tsx to detect 3rd party login/signup and create the internal user profile.

/**
 * Checks whether there is a 3rd-party signup in progress and creates the user
 * profile in the db if so.
 */
export const useThirdPartySignup = () => {
  useEffect(() => {
    const saveUserProfile = async () => {
      const { user } = await firebase.auth().getRedirectResult()
      if (!user) {
        return
      }
      const nameArray = user.displayName?.split(' ')
      fetch(Paths.API_USERPROFILES_CREATE, {
        method: 'POST',
        body: JSON.stringify({
          firebaseId: user?.uid,
          firstName: nameArray && nameArray[0],
          lastName: nameArray && nameArray.slice(1).join(' '),
          email: user.email,
        }),
      })
    }
    if (firebase.apps.length) {
      saveUserProfile()
    }
  }, [])
}
kmjennison commented 1 year ago

The line you're pointing to is here in source: https://github.com/gladly-team/next-firebase-auth/blob/4e70bb70b81702a3dfc9e05baf5fc8d25b77f6df/src/useFirebaseUser.js#L46

It's called when the Firebase JS SDK triggers an ID token changed and the user is not logged in.

I'm a little confused about how you're testing staging and prod apps. Are they on separate domains or the same domain? If they're on the same domain, we'd expect the auth state to be shared between the apps. If they're on different domains, there should not be any interaction between the two apps.

nmalancea commented 1 year ago

Thank you, @kmjennison. That helps.

They are on completely different domains, but they share the same db & the same Firebase project (for now). It's not just a matter of stage vs prod, but also between different subdomains for individual builds generated by Vercel.

What can I do to separate the apps more in terms of auth state?

I tried prefixing the cookie name differently, e.g. instead of ExampleApp (from the example config) changed to a different cookie name for one of the custom builds, but it was still interfering with production, if that was open in a different tab (as explained in the repro steps), resulting in the broken state.

kmjennison commented 1 year ago

@nmalancea My recommendation is to break out a separate Firebase project for dev/staging. I'm guessing this will solve your problem.

Closing this as likely unrelated to next-firebase-auth, but feel free to follow up, and I'll help where I'm able.