firebase / firebase-js-sdk

Firebase Javascript SDK
https://firebase.google.com/docs/web/setup
Other
4.83k stars 891 forks source link

Auth persistence fails in Safari #6072

Open h-nibor opened 2 years ago

h-nibor commented 2 years ago

[REQUIRED] Describe your environment

[REQUIRED] Describe the problem

The problem arises in Safari auth sessions lasting 1 hour or more.

Note that getAuth().currentUser returns a User.

(We do not explicitly specify authentication state persistence, so assume it is the default 'local'.)

Steps to reproduce:

  1. Sign in
  2. Leave session open for 1 hour +
  3. Call getIdToken() or reload(), or call HTTPS Callable function

    Relevant Code:

// TODO(you): code here to reproduce the problem
himanshuchawla009 commented 2 years ago

facing same issue, even before leaving session for 1 hour + on calling getIdToken() function.

jbalidiong commented 2 years ago

Hi @h-nibor, thanks for the report. I was able to reproduce the behavior now. Let me check what we can do for this issue or bring someone here that can provide more context about it. I’ll update this thread if I have any information to share.

sam-gc commented 2 years ago

Hi folks, on further investigation it looks like we're not able to reproduce this particular issue. Would you mind sharing more details about your setup?

It would be super helpful if you could share a minimal example of the code that's failing. Thanks!

h-nibor commented 2 years ago

Hi @sam-gc ,

We are bundling with Webpack and uglifying with Grunt.

We are calling getIdToken free-floating.

Here is a basic code example that causes an error to be thrown.

Please let me know if you need anything more.

const { initializeApp } = require('firebase/app');
const {
    getAuth,
    getIdToken,
    signInWithEmailAndPassword,
} = require('firebase/auth');

const FIREBASE_CONFIG = {
  projectId: '### CLOUD FUNCTIONS PROJECT ID ###',
  apiKey: '### FIREBASE API KEY ###',
  authDomain: '### FIREBASE AUTH DOMAIN ###',
};

let initialised = false;

function lazyInit() {
    if (!initialised) initializeApp(FIREBASE_CONFIG);
    initialised = true;
}

function signIn(email, password) {
    lazyInit();
    signInWithEmailAndPassword(getAuth(), email, password);
}

function getCurrentIdToken() {
    lazyInit();
    const currentUser = getAuth().currentUser;
    if (!currentUser) return Promise.resolve(null);

    return getIdToken(currentUser);
}

// If we sign in, wait for the token to expire, then try to get the token
// we expect a new token to be returned.

Here is another example where we try to call a cloud function that requires authentication. In this case we see an unauthenticated error thrown.

Client code:

const { initializeApp } = require('firebase/app');
const {
    getAuth,
    getFunctions,
    signInWithEmailAndPassword,
} = require('firebase/auth');
const {
    getFunctions,
    httpsCallable,
} = require('firebase/functions');

const FIREBASE_CONFIG = {
  projectId: '### CLOUD FUNCTIONS PROJECT ID ###',
  apiKey: '### FIREBASE API KEY ###',
  authDomain: '### FIREBASE AUTH DOMAIN ###',
};
const FIREBASE_REGION = 'europe-west2';

let app;

function getApp() {
    if (!app) app = initializeApp(FIREBASE_CONFIG);
    return app;
}

function signIn(email, password) {
    getApp();
    signInWithEmailAndPassword(getAuth(), email, password);
}

function getAuthStatus() {
    const functions = getFunctions(getApp(), FIREBASE_REGION);
    const callable = httpsCallable(functions, 'getAuthStatusEurope');
    return callable().then(response => response.data);
}

// If we sign in, wait for the token to expire, then call a cloud function
// that requires authentication, we expect the auth condition to be satisfied.

Firebase functions code:

const functions = require('firebase-functions');

function getAuthStatus(_data, context) {
    if (!context.auth) {
       throw new functions.https.HttpsError(
           'unauthenticated',
           'The function must be called while authenticated.'
       );
   }

   return 'ok';
}

exports.getAuthStatusEurope = functions
    .region('europe-west2')
    .https
    .onCall(getAuthStatus);
h-nibor commented 2 years ago

Hello,

I'd be keen to know whether anyone was able to reproduce the issue, in the end.

I'd be grateful for any update about the status of this ticket, thanks!

Edit: please let me know if there's anything more I can do to help reproduce this issue on your end, @sam-gc .

fosteman commented 1 year ago

Same issue here. Totally different setup. Safari 16.2, MacOS

"firebase": "^9.22.0",
  "firebaseui": "^6.0.2",

Using import firebase from "firebase/compat/app";
import "firebase/compat/auth";
this.auth = firebase.auth();
this.firebaseUI =
      firebaseui.auth.AuthUI.getInstance() ||
      new firebaseui.auth.AuthUI(this.auth);
const uiConfig = {
      signInSuccessUrl: config.signInSuccessUrl,
      signInOptions: [
        {
          provider: firebase.auth.GoogleAuthProvider.PROVIDER_ID,
        },
        firebase.auth.EmailAuthProvider.PROVIDER_ID,
      ],
    };
    Core.firebaseUI.start("#firebaseui-auth-container", uiConfig);

 this.auth.onAuthStateChanged((user) => {
      console.log(user); // always returns null
})
alexponoran commented 1 year ago

Same issue here. Safari 16.4, macOS Ventura 13.3.1, SvelteKit 1.18.0 and Firebase 9.22.0

If I sign out, then login again, the problem disappears. So it seems to only happen on sessions longer than 1 hour, like described in this issue.

This is the client initialization:

import { memoize } from 'lodash';

export const initFirebase = memoize(() => { const app = initializeApp(firebaseConfig); const auth = getAuth(app); return { app, auth }; });

export function getCurrentUser(auth: Auth) { return new Promise((resolve, reject) => { const unsubscribe = auth.onAuthStateChanged((user) => { unsubscribe(); resolve(user); }, reject); }); }

And this is the code on the route where I discovered the issue:

onMount(async () => { const { auth } = initFirebase(); const user = (await getCurrentUser(auth)) as User; idToken = await user.getIdToken(); });

The error I get in the Safari Console is the following: TypeError: null is not an object (evaluating $.getIdToken())

TheNotorius0 commented 1 year ago

I have the same problem. When I call getIdToken it gives me the undefined is not an object (evaluating t._canInitEmulator=!1) error too (on Safari / iPhone only):

I've noticed that until the Firebase 8.10.1 version it all works fine, but from the 9.0.0-beta.2 (firebase-auth-compact-min.js) version it has this bug.

Something is broken with the compact version probably.

UPDATE: It seems that only the minified version has this bug. So, if you are using (for example) the file firebase-auth-compat.min.js just switch to firebase-auth-compat.js and it should work

gpawlik-cais commented 6 months ago

Any follow up on this? Seems like a serious issue