90

When trying to check if a user is signed in via firebase.auth().currentUser like this:

if (firebase.auth().currentUser === null) {
  console.log('User not signed in');
}

Whenever I refresh the page, or navigate around the above returns null (even though I have just logged in).

The weird thing is, that if I log

console.log(firebase.auth().currentUser) // This returns null
console.log(firebase.auth()) // Here I can inspect the object and currentUser exists...!

I don't really know what's going on here. I'm using React and Redux, but it shouldn't really matter I'd say.

Is there a small delay where the firebase initialises and you can't access the currentUser? If so, how can I see it in the log output of firebase.auth()?

2
  • 2
    I've written a blog post about this. Don't depend on currentUser. Oct 14, 2020 at 7:09
  • console.log(someObject) will show you a compacted view of the properties of someObject at that moment. But when you expand it by clicking it in the console, it will show you its current values, at the moment of clicking. So console.log(firebase.auth()) will show you currentUser: null, but when you manage to click on it (800 ms later), Firebase has authenticated you and currentUser will have a value. You can try doing it with internet turned off and you'll see it remains null. Or you can do console.log({...firebase.auth()}) which will not update its value.
    – Juan Perez
    Mar 1 at 23:10

10 Answers 10

92

This is a commonly asked question. https://firebase.google.com/docs/auth/web/manage-users You need to add an observer to onAuthStateChanged to detect the initial state and all subsequent state changes,

firebase.auth().onAuthStateChanged(function(user) {
  if (user) {
    // User is signed in.
  } else {
    // No user is signed in.
  }
});
6
  • 23
    This it to get notified about an auth state change, it doesn't provide me with a way to check if a user is auth at any given time
    – Chris
    Jun 18, 2016 at 21:30
  • 13
    Actually this will always fire when the initial state is determined. Afterwards it will trigger when auth state changes. It is the recommended way to detect the initial auth state. You could remove it after you determine the initial state. var unsubscribe = auth1.onAuthStateChanged(function(user) { // do init stuff and then unsubscribe; unsubscribe();});
    – bojeil
    Jun 20, 2016 at 7:06
  • @Chris See my answer below. It gives an example of how to use google firebase with vuex and vuex-persistedstate in a way that will suit your requirements. Aug 3, 2018 at 23:27
  • const user = firebase.auth().currentUser; if (user) { // do something return; } // sometimes there is a delay in getting the authed user, so we must wait for the auth state change to fire firebase.auth().onAuthStateChanged(function (user) { unsubscribe(); // unsubscribe so we don't observe forever if (user) { // do something } else { alert('You are not logged in.') } });
    – Meanman
    Nov 29, 2018 at 18:02
  • 6
    I still find it strange how on the first console.log the object exists with the current user key, and on the second console.log you cannot access that key. It would appear that by the first time that console.log is executed, the object is available to the browser so why does it not allow one to select anything from that object?
    – user8284384
    Sep 13, 2019 at 9:39
14

A simple way is to add a pending state.

Here is a react example using hooks:

// useAuth.ts

import { useState, useEffect } from 'react'
import { auth } from 'firebase'

export function useAuth() {
  const [authState, setAuthState] = useState({
    isSignedIn: false,
    pending: true,
    user: null,
  })

  useEffect(() => {
    const unregisterAuthObserver = auth().onAuthStateChanged(user =>
      setAuthState({ user, pending: false, isSignedIn: !!user })
    )
    return () => unregisterAuthObserver()
  }, [])

  return { auth, ...authState }
}

// SignIn.tsx

import React from 'react'
import { StyledFirebaseAuth } from 'react-firebaseui'
import { useAuth } from '../hooks'

export default function SignIn() {
  const { pending, isSignedIn, user, auth } = useAuth()

  const uiConfig = {
    signInFlow: 'popup',
    signInOptions: [
      auth.GoogleAuthProvider.PROVIDER_ID,
      auth.FacebookAuthProvider.PROVIDER_ID,
    ],
  }

  if (pending) {
    return <h1>waiting...</h1>
  }

  if (!isSignedIn) {
    return (
      <div>
        <h1>My App</h1>
        <p>Please sign-in:</p>
        <StyledFirebaseAuth uiConfig={uiConfig} firebaseAuth={auth()} />
      </div>
    )
  }

  return (
    <div>
      <h1>My App</h1>
      <p>Welcome {user.displayName}! You are now signed-in!</p>
      <a onClick={() => auth().signOut()}>Sign-out</a>
    </div>
  )
}

1
  • When changing pages with navigate('/pathToGo'), why is useEffect in useAuth called everytime? That's not efficient and I have been looking for a solution for months. Let me know if you know something. 谢谢你的回答!
    – Watanabe.N
    Mar 10, 2022 at 6:20
12

The best way to always have access to currentUser is to use vuex and vuex-persistedstate

//Configure firebase
firebase.initializeApp(firebaseConfig);
//When ever the user authentication state changes write the user to vuex.
firebase.auth().onAuthStateChanged((user) =>{
    if(user){
        store.dispatch('setUser', user);
    }else{
        store.dispatch('setUser', null);
    }
});

The only issue above is that if the user presses refresh on the browser the vuex state will be thrown away and you have to wait for onAuthStateChange to fire again, hence why you get null when you try to access currentUser.

The secret to the above code working all the time is to use vuex-persisted state.

In your store.js file

import Vue from 'vue'
import Vuex from 'vuex'
import firebase from 'firebase/app'
Vue.use(Vuex)
import createPersistedState from "vuex-persistedstate";
export default new Vuex.Store({
    plugins: [createPersistedState()],
  state: {
    user: null
  },
    getters:{
      getUser: state => {
          return state.user;
      }
    },
  mutations: {
    setUser(state, user){
      state.user = user;
    }
  },
  actions: {
    setUser(context, user){
        context.commit('setUser', user);
    },
    signIn(){
        let provider = new firebase.auth.GoogleAuthProvider();
        firebase.auth().signInWithPopup(provider).then(function (result) {
      })
    },
    signOut(){
        firebase.auth().signOut();
    }
  }
})

You can now protect routes in your router as in the code example below.

import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
import Search from '@/components/Search/Search'
import CreateFishingSite from '@/components/FishingSites/CreateFishingSite'
Vue.use(Router);
import store from './store'
import firebase from 'firebase'

let router = new Router({
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
      {
          path: '/search/:type',
          name: 'Search',
          component: Search
      },
      {
          path: '/fishingsite/create',
          name: 'CreateFishingSite',
          component: CreateFishingSite,
          meta: {
              requiresAuth: true
          }
      }

  ]
})

router.beforeEach(async (to, from, next)=>{
    let currentUser = store.state.user;
    console.log(currentUser);
    let requriesAuth = to.matched.some(record => record.meta.requiresAuth);
    if(requriesAuth && !currentUser){
        await store.dispatch('signIn');
        next('/')
    }else{
        next()
    }
})
1
  • 1
    I was using onAuthStateChanged to fetch and set token and user again in vuex store on reload. I then implemented vuex-persistedstate. I now have a race condition where the onAuthStateChanged fires before the vuex-persistedstate. I don't want to disable the calls in onAuthStateChanged completely as they are used during the login and signup too and not just reloads. What would be the right way to implement this? Apr 30, 2020 at 7:56
3

If you are looking for a copy and paste Auth route for react with firebase:

const AuthRoute = ({ component: Component, ...rest }) => {
      const [authenticated, setAuthenticated] = useState(false)
      const [loadingAuth, setLoadingAuth] = useState(true)

      useEffect(() => {
        firebase.auth().onAuthStateChanged((user) => {
          if (user) {
            setAuthenticated(true)
          } else {
            setAuthenticated(false)
          }
          setLoadingAuth(false)
        })
      }, [])
      return loadingAuth ? 'loading...' : (
        <Route
          {...rest}
          render={props =>
            authenticated ? (
              <Component {...props} />
            ) : (
              <Redirect to={{ pathname: '/user/login' }} />
            )}
        />

      )
    }
2

Promise-wise, there are three options:

UPDATE: 11/26/22

For Firebase 9+, you could do:

Note: (this.auth) is the Auth object and depends on your framework.

const user1 = await firstValueFrom(authState(this.afa));

const user2 = await firstValueFrom(
  new Observable(observer => onAuthStateChanged(this.afa, observer))
);

const user3 = this.afa.currentUser;

// best option

const user1 = await new Promise((resolve: any, reject: any) =>
firebase.auth().onAuthStateChanged((user: any) =>
  resolve(user), (e: any) => reject(e)));

console.log(user1);

// sometimes does not display correctly when logging out

const user2 = await firebase.auth().authState.pipe(first()).toPromise();

console.log(user2);

// technically has a 3rd state of 'unknown' before login state is checked

const user3 = await firebase.auth().currentUser;

console.log(user3);
2
  • Thanks for this. Can you provide some explanation around the use of comma separated return values in option 1? Where does e come from? It looks as though both resolve and reject are being called, which isn't possible.
    – Jack
    Nov 26, 2022 at 16:32
  • Jack, the dots were how firebase worked before V9. The other is standard JS Promises, although you don't really need it, but you could use it - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
    – Jonathan
    Nov 26, 2022 at 20:56
2

TL;DR:

For people who come here using expo sdk >=48 or after updating React Native to >=0.71, you need to initialize firebaseAuth with a custom storage like so:

import { initializeAuth } from 'firebase/auth';
import { getApp } from 'firebase/app';
import { getReactNativePersistence } from 'firebase/auth/react-native';
import ReactNativeAsyncStorage from '@react-native-async-storage/async-storage';

const storage = getReactNativePersistence(ReactNativeAsyncStorage)
const app = getApp();

initializeAuth(app, {
  persistence: storage,
});

Explanation:

AsyncStorage got removed from React Native in V 0.71 as it was deprecated for a long time now. Unfortunately firebase auth is still relying on the integrated AsyncStorage under the hood.

The result is, that (even when using onAuthStateChanged as explained in the answer above) getAuth().currentUser will always be null after closing the app and opening it again.

The issue is explained in more detail here: https://github.com/firebase/firebase-js-sdk/pull/7128

0
  // On component load.
  componentDidMount = () => this.getAuthStatus();

  // Get firebase auth status.
  getAuthStatus = () => {
    firebase.auth().onAuthStateChanged((resp) => {

        // Pass response to a call back func to update state
        this.updateUserState(resp);
    });
  }

  // update state
  updateUserState = (resp) => {
     this.setState({
         user: resp
     })
  }

  // Now you can validate anywhere within the component status of a user
  if (this.state.user) { /*logged in*/}
0

Best approach for this is to use a promise and only instantiate the router after the response, something along the lines of:

store.dispatch('userModule/checkAuth').then(() => {
  // whatever code you use to first initialise your router, add it in here, for example
  new Vue({
    router,
    store,
    render: h => h(App)
  }).$mount('#app')
})

inside the checkAuth action is where you would have your promise, like so:

checkAuth ({ commit }) {
return new Promise((resolve, reject) => {
  firebase.auth().onAuthStateChanged(async (_user) => {
    if (_user) {
      commit('setUser', _user)
    } else {
      commit('setUser', null)
    }
    console.log('current user in checkAuth action:', _user)
    resolve(true)
  })
})

h/t to aaron k saunders - the source of this solution for me.

0

If you'd like the user to access to a certain page only if he is authenticated and to redirect to the home page if he is not, the following codes might help:

in React: make a component with the following code:

import { onAuthStateChanged } from "@firebase/auth";
import { Route, Redirect } from "react-router-dom";
import { auth } from "../firebase/config";
import { useState, useEffect } from "react";

const GuardedRoute = ({ component, path }) => {
  const [authenticated, setAuthenticated] = useState(false);
  const [authCompleted, setAuthCompleted] = useState(false);
  useEffect(() => {
    onAuthStateChanged(auth, (user) => {
      if (user) {
        setAuthenticated(true);
      } else {
        setAuthenticated(false);
      }
      setAuthCompleted(true);
    });
  }, []);
  return authCompleted ? (
    authenticated ? (
      <Route path={path} component={component} />
    ) : (
      <Redirect to="/" />
    )
  ) : (
    ""
  );
};
export default GuardedRoute;

and in app.js use:

import RouterPage from "./pages/RouterPage";
<GuardedRoute path="/router-page" component={RouterPage} />

in Vue: at the router file use:

const guardSuccess = (to, from, next) => {
  let gUser = auth.currentUser
  if (gUser) {
    next()
  } else {
    next({ name: "Home" })
  }
}

and in the routes of the page you want to restrict access to add:

 {
    path: "/router-page",
    name: "routerPage",
    component: () => import("../views/routerPage.vue"),
    beforeEnter: guardSuccess
  }
-3
firebase.auth().onAuthStateChanged(function(user) {
    if (user) {

      var user = firebase.auth().currentUser;


      if(user != null){ 
        var io=user.uid;
        window.alert("success "+io);




      }

    } else {
      // No user is signed in.
      Window.reload();

    }
  });

first check if user exist then get it id by

firebase.auth().currentUser.uid

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.