Home > Blockchain >  (Firebase Web Persistence) After sign in, currentUser is null, but, upon inspection of the auth obje
(Firebase Web Persistence) After sign in, currentUser is null, but, upon inspection of the auth obje

Time:09-20

I'm trying to access the currently signed in user via getAuth(app).currentUser, but the result I get is super weird, it says the auth.currentUser is null, but, then, upon inspection of the object, it isn't???

enter image description here

I did try to manually change persistence to see if it changed anything (following the docs), but it didn't make any difference.

My code (TypeScript) is pretty much this (I'm using the Firebase Emulator):

// Global Variables
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);

...

await signInWithEmailAndPassword(auth, username, password);

For those who wish to see the complete code... Here it is... It's a bit experimental, and, for this project, I'm trying to create the most minimalistic version of this that I can think of. I think the code below with a bit of HTML is pretty much what you need to reproduce it locally.

This is how I'm initializing my Firebase app and auth, which only need to exist in the admin page currently:

import { FirebaseApp, initializeApp } from "firebase/app";
import {
  Auth,
  // browserLocalPersistence,
  connectAuthEmulator,
  getAuth,
} from "firebase/auth";

import { EnvState, envState } from "../infra/env";

export const firebaseConfig = {...};

export let app: FirebaseApp;
export let auth: Auth;

let authInitialized = false;
export const initAuth = async () => {
  try {
    if (!authInitialized) {
      app = initializeApp(firebaseConfig);
      auth = getAuth(app);
      // await auth.setPersistence(browserLocalPersistence);
    
      if (envState === EnvState.dev)
        connectAuthEmulator(auth, "http://localhost:9094", {
          disableWarnings: true,
        });
    }

    authInitialized = true;
  } catch (error) {
    authInitialized = false;
  }
};

And this is the admin view (I'm usign HTML components in this project):

import { onAuthStateChanged, signInWithEmailAndPassword } from "firebase/auth";
import { auth, initAuth } from "../../infra/firebase_config";

export default class AdminView extends HTMLElement {
  static readonly tag: string = "admin-view";
    
  constructor() {
    super();

    // Could be elsewhere, doesn't seem to make a difference (not even with `await`).
    initAuth();
  }

  async connectedCallback() {
    document.title = "Admin";

    // An attempt at trying to get the current user in a different way...
    let currentUser;
    console.log(auth);
    onAuthStateChanged(auth, (user) => {
      if (user) currentUser = user;
    });
    // *********************************************
    // This is the log the picture refers to:
    // *********************************************
    console.log(currentUser);

    if (auth.currentUser) {
      this.innerHTML = `
        <p>You're logged in as ${auth.currentUser}</p>
      `;
    } else {
      this.signInForm();
    }
  }

  signInForm = (): void => {
    this.innerHTML = `
      <form>
        <fieldset>
          <label for="username">Admin</label>
          <input type="text" name="username" autofocus/>
        </fieldset>
      
        <fieldset>
          <label for="password">Password</label>
          <input type="text" name="password"/>
        </fieldset>
        
        <button type="submit">Sign in</button>
      </form>
    `;

    const submitButton: HTMLButtonElement = this.querySelector("button")!;
    submitButton.addEventListener("click", async (e: Event) => {
      e.preventDefault();
      const isLoggedIn = await this.signIn();

      const msg = isLoggedIn
        ? "You're logged in!"
        : "You weren't able to log in...";
      this.innerHTML  = `
        <p>${msg}</p>
      `;
    });
  };

  signIn = async (): Promise<boolean> => {
    const adminUserInput: HTMLInputElement = this.querySelector(
      "input[name=username]"
    )!;
    const adminPasswordInput: HTMLInputElement = this.querySelector(
      "input[name=password]"
    )!;

    const username = adminUserInput.value;
    const password = adminPasswordInput.value;

    try {
      const cred = await signInWithEmailAndPassword(auth, username, password);

      if (cred) return true;
      else return false;
    } catch (error) {
      const e = error as Error;
      console.log(e.message);

      return false;
    }
  };
}

CodePudding user response:

1. Conceptual Explanation

Even though the function signatures on some of the auth methods are not represented as asynchronous, they pretty much are. So, upon cold start of your app (or page refresh), the current user will be null. So, in the end, we have actually 3 states for the current user: unknown, signed in, not signed in. Check out Doug Stevenson's article for more info on this topic. That's why it's recommended to use a listener for tracking your current user, so it gets updated as soon as possible.

This asynchronicity is actually what causes the weirdness of your screenshot. The console.log is emitted when the user is in the unknown state and, thus is shown as null. However, when you inspect the object by clicking on the GUI's arrow, the current user has been updated in the meantime.

2. A Way of Making It Work

The console.log(currentUser); in your code would mostly be null as onAuthStateChanged() may not have loaded the auth state yet. It's best to add that observer when your page loads, perhaps in your constructor (or connectedCallback) like this:

constructor() {
  super();

  onAuthStateChanged(auth, (user) => {
    if (user) {
      this.innerHTML = `
        <p>You're logged in as ${userInfo}</p>
      `;
    } else {
      this.signInForm();
    }
  });
}
  • Related