firebase-js-sdk
firebase-js-sdk copied to clipboard
Auth persistence fails in Safari
[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()
orreload()
, passing in the result ofgetAuth().currentUser
, throws an errorundefined 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:
- Sign in
- Leave session open for 1 hour +
- Call getIdToken() or reload(), or call HTTPS Callable function
Relevant Code:
// TODO(you): code here to reproduce the problem
facing same issue, even before leaving session for 1 hour + on calling getIdToken() function.
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.
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!
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);
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 .
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
})
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())
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
Any follow up on this? Seems like a serious issue