Home > front end >  Why 'user' is always 'null' in onAuthStateChanged (Firebase 9 Vue 3 Pinia)?
Why 'user' is always 'null' in onAuthStateChanged (Firebase 9 Vue 3 Pinia)?

Time:04-26

I have a problem with Firebase's onAuthStateChanged which no matter what I've tried always returns the user as null after page reload. The required behavior is to auto-login user after page reload.

That is the first time I use the modular version of Firebase 9 and Pinia as a store.

I've already tried to use setPersistence (LOCAL) but the result is the same.

I'm stuck for the whole day and cannot figure out why it's not working.

I'd be grateful for pointing out what I'm missing there.

Packages I use in my package.json:

"dependencies": {
    "firebase": "^9.6.11",
    "pinia": "^2.0.13",
    "vue": "^3.2.13",
    "vue-router": "^4.0.3"
  }

main.ts file:

import { createApp, App as VueApp } from 'vue';
import { createPinia } from 'pinia';
import { auth } from '@/firebase';
import { onAuthStateChanged, User } from 'firebase/auth';
import { useAuthStore } from '@/store/auth';

let app: VueApp;

function initializeApp(user: User | null) {
  app = createApp(App)
    .use(createPinia())
    .use(router);

  if (user) {
    // PROMEM IS THERE -> user IS ALWAYS null
    // I want to set logged user before app is mounted
    const authStore = useAuthStore();
    authStore.handleAuthStateChange(user);
  }

  app.mount('#app');
}

onAuthStateChanged(
  auth,
  (user: User | null) => {
    // PROMEM IS THERE -> user IS ALWAYS null
    if (!app) {
      initializeApp(user);
    } else {
      const authStore = useAuthStore();
      authStore.handleAuthStateChange(user);
    }
  },
  (error: Error) => {
    log.error(`Main AuthStateChange handler failed with error`, error);
  },
);

firebase.ts file:

import { FirebaseApp, initializeApp } from 'firebase/app';
import { Auth, initializeAuth, debugErrorMap } from 'firebase/auth';
import { firebaseConfig } from '@/config';

export const app: FirebaseApp = initializeApp(firebaseConfig);
export const auth: Auth = initializeAuth(app, { errorMap: debugErrorMap });

auth.ts -> store file:

import { defineStore } from 'pinia';
import { LoginCredentials, SignUpCredentials } from '@/types/auth';
import { FirebaseAuthenticationService, AuthenticationService } from '@/service/AuthenticationService';
import { auth as firebaseAuth } from '@/firebase';
import { log } from '@/service/LoggerService';

export interface AuthStoreUser {
  uid: string,
  email: string | null
}

export type MaybeAuthStoreUser = AuthStoreUser | null;

export interface AuthStoreState {
  user: AuthStoreUser | null,
}

export const authStoreFactory = ($auth: AuthenticationService) => defineStore('auth', {
  state: () => ({
    user: null,
  } as AuthStoreState),
  getters: {
    isUserLoggedIn(): boolean {
      return !!this.user;
    },
  },
  actions: {
    async signUpUser(credentials: SignUpCredentials) {
      const createdUser = await $auth.createUserWithEmailAndPassword(credentials);
    },
    async loginUser(credentials: LoginCredentials) {
      const user = await $auth.signInWithEmailAndPassword(credentials);
    },
    async setCurrentUser(user: AuthStoreUser) {
      this.user = user;
    },
    async clearCurrentUser() {
      this.user = null;
    },
    async logoutUser() {
      await $auth.signOut();
    },
    async sendPasswordResetEmail(email: string) {
      await $auth.sendPasswordResetEmail(email);
    },
    async handleAuthStateChange(user: MaybeAuthStoreUser) {
      if (user) {
        log.debug(`Logging in user from authStateChange handler`);
        this.setCurrentUser(user);
      } else {
        log.debug(`AuthStateChange handler did not receive current user.`);
        this.clearCurrentUser();
      }
    },
  },
});

export const useAuthStore = () => {
  const $auth = new FirebaseAuthenticationService(firebaseAuth);
  return authStoreFactory($auth)();
};

CodePudding user response:

I've finally solved this problem by adding persistence option to initializeAuth. And documentation for this part is misleading and does not explain properly how it should be done with initializeAuth method...

This is how auth should be initialized in firebase.ts file:

import { firebaseConfig } from '@/config';
import { FirebaseApp, initializeApp } from 'firebase/app';
import { 
  Auth, 
  initializeAuth, 
  debugErrorMap,
  indexedDBLocalPersistence, 
  browserLocalPersistence } from 'firebase/auth';

export const app: FirebaseApp = initializeApp(firebaseConfig);
export const auth: Auth = initializeAuth(app, { 
  persistence: [indexedDBLocalPersistence, browserLocalPersistence],
  errorMap: debugErrorMap 
});

It is interesting what I've found in docs:

The default for web browser and React Native apps is local (provided the browser supports this storage mechanism, eg. 3rd party cookies/data are enabled) whereas it is none for Node.js backend apps.

And this is what I've found in auth-public.d.ts file:

export declare interface Dependencies {
    /**
     * Which {@link Persistence} to use. If this is an array, the first
     * `Persistence` that the device supports is used. The SDK searches for an
     * existing account in order and, if one is found in a secondary
     * `Persistence`, the account is moved to the primary `Persistence`.
     *
     * If no persistence is provided, the SDK falls back on
     * {@link inMemoryPersistence}.
     */
    persistence?: Persistence | Persistence[];
    // other stuff
}

If no persistence is provided, the SDK falls back on inMemoryPersistence

And inMemoryPersistence means NONE persistence. And that was cause of my problem.

  • Related