Skip to content

Users get logged out and remain in invalid authentication state #3885

Closed
@SteveBurkert

Description

@SteveBurkert

[READ] Step 1: Are you in the right place?

We use FirebaseAuthentication, which is part of the sdk, so I think yes.

[REQUIRED] Step 2: Describe your environment

  • Android Studio version: Android Studio Chipmunk | 2021.2.1 Patch 1 Build #AI-212.5712.43.2112.8609683
  • Firebase Component: Authentication
  • Component version: 21.0.1 with bom 29.1.0

Updated dependencies in the affected releases:

com.google.android.gms:play-services-auth:20.1.0 -> com.google.android.gms:play-services-auth:20.2.0
com.android.tools.build:gradle:7.0.4 -> com.android.tools.build:gradle:7.1.3
com.google.firebase:firebase-bom:29.1.0 -> com.google.firebase:firebase-bom:29.3.1

[REQUIRED] Step 3: Describe the problem

TL;DR:
Users loose their registration and end up in an invalid anonymous registered state.
FirebaseAuth.getCurrentUser probably returns null, even though there is a user and
auth is initialized. FirebaseUser.isAnonymous returns false, even though the account is
only registered using anon registration.

Long:

It's really not easy to describe the problem, so I'll try to explain the timeline and setup.

We use Firebase for authentication and it's realtime database to build a community
feature within our application, where users have profiles.
On first app start, users get signed in anonymously, until they decide to use a real auth provider,
after which the anon and "real" account get linked. So far, so normal.

After a new release we noticed within our internal test flavor, that some of the registred (non anon) users
loose their names and profiles and can no longer interact with the community.
We also noticed thousands of database permission excpetions and almost the same amount
of FirebaseNetworkExceptions.

I investigated a company owned device that was affected and it actually turns out
that the affected "user" belongs to an anonymous regsitration (UID -> console),
although the owner of the phone was registered in our community through google since 1.5 years before that.

The only option to go back from a google/facebook etc registration to an anonymous registration,
is through logging out!
And if you log out, we sign you back in anonymously, but we will not make DB calls anymore,
since FirebaseUser.isAnonymous returns true in this case.

But in our case here, the client "thinks" that it still has a profile and is normally registered (google/facebook etc.).
Therefore the client always tries to read and write DB notes, which are restricted for anon users
and which we code-wise prohibit by calling the FirebaseAuthentication typicall methods.

Till today, we have almost like 10k affected users.

Steps to reproduce:

The only way I see that a normal registered user is "again anonymous", (and which I was
kind of able to reproduce), would be if FirebaseAuth.getCurrentUser returns null,
even though there is a normal registered account.
In this case we would wrongly call signInAnonymously OVER the current registration.

I tried it, and if you do this, the FirebaseUser will end up in an invalid state and return FALSE for
FirebaseUser.isAnonymous, even though getProviderData will only return 1 entry containing "firebase".
This would in our case result in executing multiple DB-write approaches, which will of course fail
and might be the reason for the thousands Database Permission Erros.

I don't know how FirebaseAuth.getCurrentUser would return null for normal account though.
Maybe the thousands occurences of FirebaseNetworkException are related to FirebaseAuth
returning null for current user.

I will provide a minimal repo, to reproduce the isAnonymous == false issue.
And I will provide a small code snippet of our authentication app startup.

Our authentication app startup procedure is very simple.

  1. Initialize Firebase auth state
  2. Check for a user
    2a. If there is non, register an anonymous account
    2b. If there is one, make a quick DB call, but only if the account is not anonymous

Relevant Code:

    // Called at app startup once
    private void initializeFirebaseAuth() {
        FirebaseAuth.getInstance().addAuthStateListener(new FirebaseAuth.AuthStateListener() {
            @Override
            public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
                firebaseAuth.removeAuthStateListener(this);
                FirebaseUser user = firebaseAuth.getCurrentUser();
                if (user != null) {
                    writeToDb(user);
                } else {
                    signInAnonymously(user);
                }
            }
        });
    }

    private void writeToDb(FirebaseUser user){
        if(user.isAnonymous){
            Timber.w("Some warning");
        }else{
            //FirebaseDatabase.someCall ...
        }
   }

    private void signInAnonymously(FirebaseUser user) {
        if(user != null){
            Timber.w("Some warning");
            return;
        }
        firebaseAuth.signInAnonymously().addOnCompleteListener(task -> {
            if (task.isSuccessful()) {
                Timber.d("Nice");
            }
        });
    }
}

Exceptions:

FirebaseNetworkException - first occurence May 5h, 2644 non-fatal events affecting 1074 users in the last 90 days

Non-fatal Exception: com.google.firebase.FirebaseNetworkException: A network error (such as timeout, interrupted connection or unreachable host) has occurred.
       at com.google.android.gms.internal.firebase-auth-api.zzti.zza(com.google.firebase:firebase-auth@@21.0.3:17)
       at com.google.android.gms.internal.firebase-auth-api.zzuc.zza(com.google.firebase:firebase-auth@@21.0.3:1)
       at com.google.android.gms.internal.firebase-auth-api.zzud.run(com.google.firebase:firebase-auth@@21.0.3:3)
       at android.os.Handler.handleCallback(Handler.java:938)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:255)
       at android.app.ActivityThread.main(ActivityThread.java:8194)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:632)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1049)

image

Which is probably causing:

DatabaseException - first occurence May 8h, 79531 non-fatal events affecting 21038 users in the last 90 days

Non-fatal Exception: com.google.firebase.database.DatabaseException: Firebase Database error: Permission denied
       at com.google.firebase.database.DatabaseError.toException(DatabaseError.java:230)
       at com.google.firebase.database.core.utilities.Utilities$1.onComplete(Utilities.java:253)
       at com.google.firebase.database.core.Repo$7.run(Repo.java:435)
       at android.os.Handler.handleCallback(Handler.java:938)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loopOnce(Looper.java:233)
       at android.os.Looper.loop(Looper.java:344)
       at android.app.ActivityThread.main(ActivityThread.java:8204)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:589)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1071)

image

Minimal repo - showcase isAnonymous wrong state

https://github.com/SteveBurkert/firbease-zombie-user-example

(The repo only showcases how you can reproduce to make FirebaseUser.isAnonymous return false, even though it is anonymously registered.)

Thanks in advance.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions