Firebase Auth

Why is my currentUser == null in Firebase Auth?

Doug Stevenson
Oct 9, 2020 · 6 min read
Why is my current user equal to null?
Why is my current user equal to null?

If I had to hazard a guess about the most frequent point of confusion when starting with Firebase Authentication, it would be the currentUser API. While it seems simple enough to get an object representing the currently signed in user, there is one hidden complexity.

Auth state seems binary: null or non-null (or is it?)

Here’s an easy enough bit of code in JavaScript that checks to see if a user is signed in:

const user = firebase.auth().currentUser
if (user) {
// user is signed in, show user data
}
else {
// user is signed out, show sign-in form
}

Here’s the equivalent in Java:

FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null) {
// user is signed in, show user data
}
else {
// user is signed out, show sign-in form
}

And Swift:

let user = Auth.auth().currentUser
if user != nil {
// user is signed in, show user data
}
else {
// user is signed out, show sign-in form
}

In all three of these cases, an object property or accessor method is used to check the state of the currently signed in user. While this works sometimes, you might notice that it doesn’t work as expected when the app is launched cold, or the web page is refreshed. The current user object ends up being null, even if the user previously signed in.

This is extra confusing given that the documentation suggests that the signed in user is automatically persisted by default between app launches and page reloads. If information about the signed-in user persists as documented, why is the object still null? And even more confusing, why only on a cold start like this?

Let’s dig in!

The current user is object is obtained asynchronously

One thing to realize about the Firebase SDKs is that its APIs are all asynchronous. This means that the SDK is not going to block the main thread of execution in order to deliver data — the object containing the currently signed in user is no exception. In fact, the Firebase Auth SDK requires at least one, and possibly two steps, to take in order to deliver a valid current user object:

  1. Check and load the persisted user ID token from disk.
  2. Refresh the user’s ID token if needed (hourly).

Both of these are blocking operations. Reading from disk obviously requires I/O, which is typically fast, but not guaranteed to be so. And, refreshing the ID token requires network I/O to refresh the ID token, if it hasn’t been in the past hour. Since these operations block indefinitely, the SDK can’t allow either of them to happen on the main thread to be available as your app loads. That would risk slowing your app’s launch time, or even cause it to hang entirely. That is, of course, very bad for your users.

The confusing part of all this is because of the fact that the currentUser API only returns two possible values: null (signed out) and non-null (signed in). So it gives you the impression that the current state of the sign-in state is binary — either one or the other. But this isn’t actually the way it works.

Auth state is actually trinary!

The way I like to think of it is like this: the current user status is actually trinary. I call these states:

  1. Unknown
  2. Definitely signed out
  3. Definitely signed in

The currentUser API only exposes two types of values: null or non-null, so you can’t tell the difference between states 1 and 2. A null value could mean either one, and we have no way to immediately verify this! This is problematic because your app probably needs logic that goes like this (in pseudocode):

switch (userState) {
case unknown:
// show a splash screen or loading indicator
case definitelySignedOut:
// show a sign in form
case definitelySignedIn:
// show main screen
}

Showing a loading indicator in the unknown state is very important, because we just don’t know how long the SDK is going to take to load and refresh that token. The user needs to be told to wait.

So, what’s the solution?

Use an auth state observer (AKA auth state listener)

The SDK allows you to receive an asynchronous callback when the user’s auth state changes. It looks like this in JavaScript, with similar callbacks for Android and iOS:

firebase.auth().onAuthStateChanged(user => {
if (user) {
// User is signed in.
}
else {
// User is signed out.
}
})

The problem here is still that the user object delivered to the callback function is still either null or non-null. But we need a way to distinguish between “unknown” and “definitely signed out”. I’ll annotate that more clearly with some log messages:

console.log("state = unknown (until the callback is invoked)")
firebase.auth().onAuthStateChanged(user => {
if (user) {
console.log("state = definitely signed in")
}
else {
console.log("state = definitely signed out")
}
})

Note that the user auth state is unknown only before the callback is invoked the first time, so you should set up your UI to show that splash screen or loading indicator before setting up this observer. And you should set it up as soon as possible after your app launches. After that, the user can only toggle between signed-in and signed-out, for as long as the app process is running. On a cold start, we have to do this all over, starting again from the unknown state.

The API for auth state observers/listeners is onAuthStateChanged in JavaScript, addAuthStateListener in Java, and addDidStateChangeListener in Swift. You should definitely always use these listener APIs instead of the synchronous currentUser APIs I showed at the top of this post.

In my opinion, it’s rather unfortunate that the SDK doesn’t expose this trinary state by design, as it leads to all that confusion about how the currentUser API works. But the good news is that you can create your own API to wrap Firebase Auth. The remainder of this article should give you some ideas.

For web: Use RxJS observables

Here is some TypeScript code that illustrates how to compose a new observable that emits each state.

enum UserStatus {
Unknown,
SignedIn,
SignedOut
}
class UserState {
readonly status: UserStatus
readonly value: firebase.User | undefined | null
constructor(value: firebase.User | undefined | null) {
this.status =
value === undefined ? UserStatus.Unknown :
value === null ? UserStatus.SignedOut :
UserStatus.SignedIn
this.value = value
}
}
const userBehaviorSubject =
new BehaviorSubject<UserState>(new UserState(undefined))
const authStateObserver = (user: firebase.User | null) => {
if (user) {
userBehaviorSubject.next(new UserState(user))
}
else {
userBehaviorSubject.next(new UserState(null))
}
}
firebase.auth().onAuthStateChanged(authStateObserver)

This code exposes a UserState object via the BehaviorSubject called userBehaviorSubject. It has an enum you can use to easily check the state and find the user object. Use this to determine how to render your web page as the auth state changes over time.

Note that the initial value of the Subject is set to unknown via the undefined value, then changes over time with updates from the Auth SDK. You can use it like this:

const userStateObserver = async (userState: UserState) => {
switch (userState.status) {
case UserStatus.SignedIn:
console.log(`${user.uid} signed in`)
break
case UserStatus.SignedOut:
console.log("User signed out")
break
default:
console.log("User status unknown")
break
}
}
userBehaviorSubject.subscribe(this.userStateObserver)

For Android: Use a LiveData

LiveData is the preferred way on Android to expose values that can change over time. Here’s some Kotlin you can use to build a LiveData that exposes Firebase Auth user state:

sealed class FirebaseAuthUserState
data class UserSignedIn(val user: FirebaseUser) : FirebaseAuthUserState()
object UserSignedOut : FirebaseAuthUserState()
object UserUnknown : FirebaseAuthUserState()
@MainThread
fun FirebaseAuth.newFirebaseAuthStateLiveData(
context: CoroutineContext = EmptyCoroutineContext
): LiveData<FirebaseAuthUserState> {
val ld = FirebaseAuthStateLiveData(this)
return liveData(context) {
emitSource(ld)
}
}
class FirebaseAuthStateLiveData(private val auth: FirebaseAuth) : LiveData<FirebaseAuthUserState>() {private val authStateListener = MyAuthStateListener() init {
value = UserUnknown
}
override fun onActive() {
auth.addAuthStateListener(authStateListener)
}
override fun onInactive() {
auth.removeAuthStateListener(authStateListener)
}
private inner class MyAuthStateListener : AuthStateListener {
override fun onAuthStateChanged(auth: FirebaseAuth) {
val user = auth.currentUser
value = if (user != null) {
UserSignedIn(user)
}
else {
UserSignedOut
}
}
}
}

This code creates an extension function on FirebaseAuth that lets you build a new LiveData that emits a sealed class with the three possible states. This LiveData could be a singleton in your app that any component can use to track user state.

Note that the initial value in the constructor is UserUnknown, then changes over time with the updates from the Auth SDK. You can use it like this:

val myUserStateObserver =
Observer<FirebaseAuthUserState> { userState ->
when (userState) {
is UserSignedOut ->
// your code
is UserSignedIn ->
// your code
is UserUnknown ->
// your code
}
}
val authStateLiveData = authRepo.authStateLiveData
authStateLiveData.observeForever(myUserStateObserver)

For iOS: Sorry, I don’t have an example!

I don’t work with iOS, so I don’t have an example to share. But if you’re reading this and come up with an iOS friendly way to implement observables, let me know and I’ll add your code here.

Firebase Developers

Tutorials, deep-dives, and random musings from Firebase…

Sign up for The Firebase Developers Quarterly

By Firebase Developers

A summary of what has happened on the Firebase Developers publication in the past quarter. Sent once a quarter. Take a look

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Thanks to Peter Friese

Doug Stevenson

Written by

firebase-consultant.com, Firebase GDE, engineer, developer advocate, Xoogler

Firebase Developers

Tutorials, deep-dives, and random musings from Firebase developers all around the world. Views expressed are those of the authors and don’t necessarily reflect those of Firebase or its parent companies.

Doug Stevenson

Written by

firebase-consultant.com, Firebase GDE, engineer, developer advocate, Xoogler

Firebase Developers

Tutorials, deep-dives, and random musings from Firebase developers all around the world. Views expressed are those of the authors and don’t necessarily reflect those of Firebase or its parent companies.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store