firebase-js-sdk icon indicating copy to clipboard operation
firebase-js-sdk copied to clipboard

Auth persistence fails in Safari

Open h-nibor opened this issue 2 years ago • 9 comments

[REQUIRED] Describe your environment

  • Operating System version: macOS Monterey 12.2.1
  • Browser version: Safari 15.3
  • Firebase SDK version: 9.6.8
  • Firebase Product: auth

[REQUIRED] Describe the problem

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

  • Calling getIdToken() or reload(), passing in the result of getAuth().currentUser, throws an error undefined is not an object (evaluating t._canInitEmulator=!1).

  • On calling a Firebase HTTPS Callable function, the auth token also fails to validate. context.auth evaluates falsy.

  • When inspecting firebaseLocalStorage contents in Indexed DB, value.stsTokenManager.expirationTime is not updated.

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

h-nibor avatar Mar 16 '22 16:03 h-nibor

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

himanshuchawla009 avatar Mar 17 '22 04:03 himanshuchawla009

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.

jbalidiong avatar Mar 17 '22 13:03 jbalidiong

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?

  • What are you using to build/deploy your app (webpack/rollup/etc)
  • Are you calling getIdToken on the user object or the free-floating version?

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

sam-gc avatar Mar 17 '22 17:03 sam-gc

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 avatar Mar 22 '22 10:03 h-nibor

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 .

h-nibor avatar Apr 25 '22 10:04 h-nibor

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
})



fosteman avatar May 24 '23 16:05 fosteman

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())

alexponoran avatar May 24 '23 22:05 alexponoran

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

TheNotorius0 avatar Jun 02 '23 07:06 TheNotorius0

Any follow up on this? Seems like a serious issue

gpawlik-cais avatar Apr 12 '24 16:04 gpawlik-cais