1

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;
    }
  };
}
Philippe Fanaro
  • 6,148
  • 6
  • 38
  • 76
  • 1
    [Can't get currentUser on load](https://stackoverflow.com/q/37883981/13130697)? Firebase SDK load auth state asynchronously so you must use `onAuthStateChanged` as in that answer. Can you share complete code so we can see where you get `null`? – Dharmaraj Sep 19 '22 at 16:34
  • I did try it with the `onAuthStateChanged` as well, it also doesn't give me the current user, apparently. And why is it called that if it has more responsibilities... I've added the complete, mostly experimental code to the question as well. – Philippe Fanaro Sep 19 '22 at 16:53
  • I find it very odd that so many of these methods and properties seem to refer to inherently asynchronous operations, as you mentioned, but don't have an async signature. Why??? – Philippe Fanaro Sep 19 '22 at 16:54

1 Answers1

1

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();
    }
  });
}
Philippe Fanaro
  • 6,148
  • 6
  • 38
  • 76
Dharmaraj
  • 47,845
  • 8
  • 52
  • 84
  • I think it's working now, but I had to put the `if (userInfo)...` inside the `onAuthStateChanged` listener actually, since the `connectedCallback` might run before any info on authentication reaches the listener (and it doesn't matter if it is inside the `connectedCallback` or the `constructor`. The `connectedCallback` method is run when the HTML Component is attached to the page on the browser, and the constructor is run when the object itself is created. – Philippe Fanaro Sep 19 '22 at 18:34
  • @PhilippeFanaro great. I wasn't sure about when the page would be rendered. Yes, called the `connectedCallback()` only when user state is loaded then. – Dharmaraj Sep 19 '22 at 18:36
  • I would like to accept your answer, but it doesn't quite explain the original screenshot I posted, as far as I understood. Do you know why the logging it was giving me `null` at the same time as having the object fully inside? – Philippe Fanaro Sep 19 '22 at 19:30
  • 1
    @PhilippeFanaro got the question. So the local auth state could be stale and once it's refreshed/loaded from server, the auth state is actually loaded. Checkout [this article](https://medium.com/firebase-developers/why-is-my-currentuser-null-in-firebase-auth-4701791f74f0) for detailed information. – Dharmaraj Sep 19 '22 at 19:43
  • Thanks for the link! Why the hell is that not a (big) part of the official docs... Kind of ridiculous. And that article is even behind a paywall... It kind of answers 90% of what I asked, thanks. But the last part of that question is still surprising to me: even the browser on falls into this asynchronous problem? Like the unexpanded object shows `null` at first and then, when I expand it, the async code has already run and then it doesn't show `null` anymore? – Philippe Fanaro Sep 19 '22 at 20:24
  • @PhilippeFanaro can you clarify which log statement in the code are you referring to? – Dharmaraj Sep 19 '22 at 20:27
  • It's the log that generated the screenshot. It's highlighted in between `// *****************` in the code in the question. Anyways, I can quickly edit (just to add some stuff) your answer and accept it if you want. – Philippe Fanaro Sep 19 '22 at 20:28
  • 1
    @PhilippeFanaro if you add `console.log("onAuthStateChanged")` inside `onAuthStateChanged` then you'll notice that the `console.log(currentUser)` runs before it and hence it's null. Because `onAuthStateChanged` is async and does not block the code execution – Dharmaraj Sep 19 '22 at 20:36
  • See if the edit is correct and not too intrusive, then I'll accept the answer :) – Philippe Fanaro Sep 19 '22 at 20:51
  • @PhilippeFanaro looks good, I'll just make some minor changes later, like general JS related things – Dharmaraj Sep 19 '22 at 20:53