Firebase: How to persist auth state? - javascript

I am using firebase web sdk without React. I have 4 pages in my application. I am using express js in for each pages to send html file of that route.
I have a file called common.js in which I defined methods that will be shared by all pages. In the same file I defined a method to act on auth changed is as following:
Modified below code
import { initializeApp } from 'firebase/app';
import { getAuth, onAuthStateChanged } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
import { firebaseConfig } from './firebase.config';
const firebaseApp = initializeApp(firebaseConfig);
const db = getFirestore(firebaseApp);
const auth = getAuth(firebaseApp);
const addNavbar = () => {
$('#nav').load('nav.html', () => {
document.getElementById('btnSignOut').addEventListener('click', (e) => {
auth.signOut();
});
});
}
// createAlert, createEditDeleteButtons, displayLoaderAndHideApp defined here
const unsubscribeOnAuthStateChanged = onAuthStateChanged(auth, async (user) => {
if (user) {
window.location.replace('/home');
} else {
window.location.replace('/login');
}
});
export {
addNavbar, auth, createAlert, createEditDeleteButtons, db, displayLoaderAndHideApp,
};
On each page I first call handleOnAuthStateChanged(), and it is also called after successful login. I want to persist the auth so I came across setPersistence(), so I used it in login.js and is as follows:
import { browserLocalPersistence, browserSessionPersistence, setPersistence, signInWithEmailAndPassword } from 'firebase/auth';
import { auth, handleOnAuthStateChanged } from './common';
document.getElementById('btnSignin').addEventListener('click', async (e) => {
e.preventDefault();
try {
const form = document.querySelector('form.formLogin');
const email = form.email.value.trim();
const password = form.password.value;
await setPersistence(auth, browserLocalPersistence);
await signInWithEmailAndPassword(auth, email, password);
handleOnAuthStateChanged(auth.currentUser); // here auth will retrieve the new logged in user
} catch (e) {
console.error(e);
}
});
If I use onAuthStateChanged() on other pages my user is null then I am stuck in a loop.
Am I missing something?
Update
All of the minimal code is after removing all the functionalities except a few that might causing the problem.
I have a file called nav.html that is loaded into page by common.js and is as defined above.
Code for home, page 1 and page 2 adds navigation by calling addNav. Home will import methods from common.js, page 2's js, and page 1's js. Home page's js is as following:
import { addDoc, collection, deleteDoc, doc, getDocs, onSnapshot, query, setDoc, where } from 'firebase/firestore';
import { addNavbar, createAlert, createEditDeleteButtons, db, displayLoaderAndHideApp as displayLoader } from './common';
// imports from page 1 and page 2
addNavbar();
// Other methods are defined here...
// Nothing is exported as of now..

Firebase automatically persists the auth state on most browser environments, and restores it on app/page reload. To detect this restore of auth state (and other auth state changes) you will need to register an auth state listener with the SDK as shown in the first code fragment in the documentation on getting the current user:
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
const uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});
Most of the code of your handleOnAuthStateChanged can probably go into this handler.

Related

Can not enableIndexedDbPersistence from firebase v9 in a nextjs pwa

I can't firebase v9 enableIndexedDbPersistence in a nextjs pwa.
this erros are trow
index.js // firebase main
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'
import { getFirestore } from 'firebase/firestore'
import { getStorage } from 'firebase/storage'
const firebaseConfig =
process.env.NEXT_PUBLIC_FIREBASE_CONFIG
const app = initializeApp(JSON.parse(firebaseConfig))
export const db = getFirestore(app)
// enableIndexedDbPersistence(db) // >>> if put here it said can't be invoked after getFirestore or any other funtion
export const auth = getAuth(app)
export const storage = getStorage(app)
So where should i invoke
The enableIndexedDbPersistence(db) function must be called before . From its documentation:
Must be called before any other functions (other than
{#link initializeFirestore}, {#link (getFirestore:1)} or
{#link clearIndexedDbPersistence}.
So getFirestore(app) is actually exempted from that, in contrast what you stated in the comment of your code snipped:
// >>> if put here it said can't be invoked after getFirestore or any other function
Consequently, my guess would be that you might use the exported db before the promise of enableIndexedDbPersistence(db) completes.
You could resolve this in different ways, such as wrapping it in a service or method and make sure you are await-ing the promise, or generally change your app that db is not used right away.
In my Ionic PWA app, I'm successfully using the following:
import { getFirestore, enableIndexedDbPersistence } from "firebase/firestore";
import { initializeApp } from "firebase/app";
const firebaseConfig = {
// ...
};
const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
enableIndexedDbPersistence(db)
.then(() => console.log("Enabled offline persistence"))
.catch((error) => {
if (error.code == "failed-precondition") {
// Multiple tabs open, persistence can only be enabled
// in one tab at a a time.
// ...
} else if (error.code == "unimplemented") {
// The current browser does not support all of the
// features required to enable persistence
// ...
}
});
This is very similar to your snipped indeed. But the first access to Firestore happens after via user interaction, and not right away.

let or React.useRef: what is the best approach?

I'm developing a REACT JS app and I've a doubt. If I have something that must not be taken as a dependency i usually put a let like in this case, where I'm initializing Firebase for my app:
import { initializeApp } from 'firebase/app';
import { initializeAppCheck, ReCaptchaV3Provider } from 'firebase/app-check';
let app = null;
const useInitializeFirebase = () => {
if (app) {
return;
}
app = initializeApp(window.FIREBASE);
initializeAppCheck(app, {
provider: new ReCaptchaV3Provider(window.RECAPTCHA_SITE_KEY),
isTokenAutoRefreshEnabled: true,
});
};
export default useInitializeFirebase;
The so exported hook is called within the main index.js file in this way:
const App = () => {
useInitializeFirebase();
...
}
So if the hook gets called multiple times (e.g. for a re-render), Firebase won't give error.
Is this the best approach? Or maybe I should use useRef or useState or even something else?
EDIT
The problem occurs also if I don't use an hook.
Take this code as reference:
import { initializeApp } from 'firebase/app';
let firebaseApp = null;
export default (firebaseConfig) => {
if (firebaseApp === null) {
firebaseApp = initializeApp(firebaseConfig);
}
return firebaseApp;
};
That can be used in this way
import { getAuth, signInWithCustomToken } from 'firebase/auth';
import firebaseApp from '#Src/helpers/firebaseApp';
const firebaseLogin = (token) => {
const auth = getAuth(firebaseApp(window.FIREBASE));
return signInWithCustomToken(auth, token);
};
export { firebaseLogin };
In this way if I don't put a guard firebase would be initialized multiple times, giving error.

Are you supposed to pass the firebase app to getAuth() or leave the arguments as blank?

The firebase docs show them using the app instance inside the getAuth() call, in one portion of the docs
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
// ...
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app); //I'm talking about this line
Then in the very next section on the same page, they go ahead and use getAuth() without the app instance, in order to sign in a user with email.
import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";
const auth = getAuth();
createUserWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
const user = userCredential.user;
})
.catch((error) => {
console.log(error)
});
What is the correct way to use it?
The getAuth() function without parameters will use the default initialized instance of Firebase. If you have multiple instances of Firebase i.e. multiple projects, then you must pass the app instance to specify which project's auth that auth instance must use.
const app1 = initializeApp(project1Config);
const app2 = initializeApp(project2Config, "app2");
const auth1 = getAuth(); // or getAuth(app1), uses app1 as it's default,
const auth2 = getAuth(app2); // uses app2

How to initialize firebase in vite and vue?

I am new in vite. I am trying to initialize the firebase app. but I am getting errors like below
Firebase: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp()
I created a file name firebase.ts but i am not really sure where can i include this to initialize firebase globally.
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useHead } from '#vueuse/head'
import { isDark } from '/#src/state/darkModeState'
import useNotyf from '/#src/composable/useNotyf'
import sleep from '/#src/utils/sleep'
import { getAuth, signInWithEmailAndPassword } from '#firebase/auth'
const isLoading = ref(false)
const router = useRouter()
const notif = useNotyf()
const username = ref('')
const password = ref('')
const handleLogin = async () => {
if (!isLoading.value) {
isLoading.value = true
signInWithEmailAndPassword(getAuth(), username.value, password.value)
.then((user) => {
isLoading.value = false
router.push({ name: 'sidebar-dashboards-course' })
})
.catch((err) => {
isLoading.value = false
notif.error(
'There is no user record corresponding to this identifier. The user may be deleted'
)
})
}
}
Any solution appreciated!
In your firebase.ts, you are initializing Firebase using the compat version (that let's you use the name-spaced syntax even in V9) but you are trying to use the Modular version in your Vue components. Instead, try initializing Firebase using modular SDK as well. So the firebase.ts should look like:
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
import { getStorage } from "firebase/storage";
const firebaseConfig = {...};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const storage = getStorage(app);
export { auth, db, storage };
Then try importing these instances in your components instead of using get[Service]() functions again.
import { auth } from "../path/to/firebase.ts" // update the path as per your dir structure
// usage: instead of getAuth() here
await signInWithEmailAndPassword(auth, username.value, password.value)

Google firebase Sign-in show no email option on first sign-in

I'm using Google firebase v9. On the first sign in, usually there would be a pop up for user to choose which email to sigh in with, but for some reason, when I sign in, it automatically send me to the chat page automatically. I tried delete user in my firebase app and tried again and get the same result. What am I doing wrong?
firebase.js:
import { GoogleAuthProvider } from 'firebase/auth';
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
const firebaseConfig = {//myap config};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const provider = new GoogleAuthProvider();
export { auth, provider };
login-page:
import { auth, provider } from '../firebase';
import { useAuthState } from 'react-firebase-hooks/auth';
import { useNavigate } from 'react-router-dom';
import { signInWithPopup } from 'firebase/auth';
function Header(){
const [user] = useAuthState(auth);
const navigate = useNavigate();
const signIn = (e) => {
signInWithPopup(auth, provider)
.then(() => navigate('/channel'))
.catch((error) => alert(error.message));
};
return (
<buttononClick={!user ? signIn : () => navigate('/channel')}>Login</button/>
);
};
Maybe It's because I am already sign-in on chrome? If someone can check my code and verified, that would be awesome.
It sounds like your app is already associated with your Google account. Signing out does not sever that association.
Removing that association is described in the Remove third-party account access, but mostly consists of going to the list of Third-party apps with account access and removing your app from that list.
If that doesn't do it, have a look at the results on forcing the account picker to show up.

Categories

Resources