firebase-android-sdk icon indicating copy to clipboard operation
firebase-android-sdk copied to clipboard

Users get logged out and remain in invalid authentication state

Open SteveBurkert opened this issue 2 years ago • 4 comments

[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.

SteveBurkert avatar Jul 10 '22 17:07 SteveBurkert

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

google-oss-bot avatar Jul 10 '22 17:07 google-oss-bot

Thanks for the detailed explanation and reproducible example, @SteveBurkert. I'll notify our engineers and see what we can do here.

argzdev avatar Jul 12 '22 14:07 argzdev

Hi, thanks for filing this issue! We are unable to promise any timeline for this, but if others also have this issue, adding a +1 on this issue can help us prioritize adding this to the roadmap.

(Googler-only internal tracking bug: b/238776633)

lisajian avatar Jul 12 '22 18:07 lisajian

We recently noticed a similar behaviour. Maybe it's related so I'm going to explain our findings:

First of all, we're using Anonymous Authentication in our app. We call signInAnonymously() during app start and use the (anonymous) user ID as a reference to user data in various Firestore collections and documents.

A few weeks ago users started complaining that they are losing their data on every app start. We looked into this and could reproduce a case where signInAnonymously() would return a new user on every call. Of course no app data was deleted between the executions. The app was just terminated and started again. Obviously with a new user ID, references to previously stored data in Firestore are lost which goes along with the user reportings. However when we manually cleared the app data and started the app, this faulty behaviour was gone and we received the same user ID on every app start. This is also confirmed by our affected users.

We assume that some update of the firebase-auth dependency introduced this erroneous state which can only be fixed by clearing application data.

Additionally we noticed a few FirebaseNetworkExceptions in Crashlytics coming from firebase-auth which could be the reason for this problem? In accordance with OP's findings it seems that most of these exceptions started to occur with version 21.0.3 of firebase-auth (no guarantees though as we can only look back up to 90 days in Crashlytics). These exceptions still occur with the current version 21.0.6. Some exemplary exception below:

Caused by 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.zztu.zza(com.google.firebase:firebase-auth@@21.0.6:17)
       at com.google.android.gms.internal.firebase-auth-api.zzus.zza(zzus.java:9)
       at com.google.android.gms.internal.firebase-auth-api.zzut.zzl(com.google.firebase:firebase-auth@@21.0.6:1)
       at com.google.android.gms.internal.firebase-auth-api.zzuq.zzh(com.google.firebase:firebase-auth@@21.0.6:25)
       at com.google.android.gms.internal.firebase-auth-api.zzts.zzh(zzts.java:1)
       at com.google.android.gms.internal.firebase-auth-api.zzqq.zza(zzqq.java:2)
       at com.google.android.gms.internal.firebase-auth-api.zzvb.zza(zzvb.java:16)
       at com.google.android.gms.internal.firebase-auth-api.zzuh.zzf(com.google.firebase:firebase-auth@@21.0.6:4)
       at com.google.android.gms.internal.firebase-auth-api.zzrx.zzp(zzrx.java:4)
       at com.google.android.gms.internal.firebase-auth-api.zztt.zzj(zztt.java:5)
       at com.google.android.gms.internal.firebase-auth-api.zzsh.zzc(com.google.firebase:firebase-auth@@21.0.6:1)
       at com.google.android.gms.internal.firebase-auth-api.zzsh.zzc$bridge(com.google.firebase:firebase-auth@@21.0.6:35)
       at com.google.android.gms.internal.firebase-auth-api.zzuu.run(com.google.firebase:firebase-auth@@21.0.6:1)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
       at java.lang.Thread.run(Thread.java:920)

We hope this information helps in identifying and fixing the problem soon 🤞🏼

svenjacobs avatar Jul 22 '22 13:07 svenjacobs

Hey there. Any updates on this issue?

We recently added some massive logging on our end and we've found that on some permission denials, related to the database rules, we got users with weird provider data, that seem to have the above described problem.

Here is one of the logs from crashlytics with a dedicated custom exception:

This list shows all the providers the user is signed up with...

Firebase SDK has illegal auth state. Is not anonymous user by SDK, but only has anon provider id. User: 'User.providers = [firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, firebase, fireb<truncated: 1267558 chars>

May I highlight <truncated: 1267558 chars> here! Each provider takes up 10 characters in space (including the comma and one whitespace). This means, that this user is roughly signed up 130 thousand times(!) with anon provider. What the...

SDK internally isAnonymous is checked also by size of the provider info array. If it's > 1 it will directly return false. Which is the case here. But how can this type of provider mess happen?

The provider id firebase comes from https://firebase.google.com/docs/reference/android/com/google/firebase/auth/FirebaseAuthProvider#PROVIDER_ID right? And FirebaseAuthProvider is only anonymous sign ups, right? So how can we end up with 20+ anonymous providers for one user? We have like thousand of users, having this issue. @lisajian

SteveBurkert avatar Feb 27 '23 15:02 SteveBurkert

Hello, I would also like to ask what the progress is on this issue? We're still seeing a lot of FirebaseNetworkException in firebase-auth, actually it's the number one crash of our app constantly occurring, but I doubt that all affected users have a bad network connection when this happens.

Caused by 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.zzxc.zza(com.google.firebase:firebase-auth@@21.1.0:19)
       at com.google.android.gms.internal.firebase-auth-api.zzya.zza(zzya.java:9)
       at com.google.android.gms.internal.firebase-auth-api.zzyb.zzl(com.google.firebase:firebase-auth@@21.1.0:1)
       at com.google.android.gms.internal.firebase-auth-api.zzxy.zzh(com.google.firebase:firebase-auth@@21.1.0:25)
       at com.google.android.gms.internal.firebase-auth-api.zzxa.zzh(zzxa.java:1)
       at com.google.android.gms.internal.firebase-auth-api.zzty.zza(zzty.java:2)
       at com.google.android.gms.internal.firebase-auth-api.zzyj.zza(zzyj.java:16)
       at com.google.android.gms.internal.firebase-auth-api.zzxp.zzf(com.google.firebase:firebase-auth@@21.1.0:4)
       at com.google.android.gms.internal.firebase-auth-api.zzvf.zzp(zzvf.java:4)
       at com.google.android.gms.internal.firebase-auth-api.zzxb.zzj(zzxb.java:5)
       at com.google.android.gms.internal.firebase-auth-api.zzvp.zzc(com.google.firebase:firebase-auth@@21.1.0:1)
       at com.google.android.gms.internal.firebase-auth-api.zzyc.run(com.google.firebase:firebase-auth@@21.1.0:1)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
       at java.lang.Thread.run(Thread.java:1012)

svenjacobs avatar Mar 30 '23 05:03 svenjacobs

Hi all, this issue is now fixed in Firebase BoM version 32.0.0 or Authentication version 22.0.0. That said, I'll close this issue now. Feel free to reply back here if there's any issue. Thanks!

argzdev avatar May 03 '23 13:05 argzdev

@argzdev Wow, that are great news, thank you! 👍🏼 What was the reason for the wrong authentication state and what was the fix? I'm curious and would like to know 😃

svenjacobs avatar May 03 '23 14:05 svenjacobs

Hi @svenjacobs, according to the internal bug link, it seems that this is the possible reason:

This issue is because if we already have a user instance and get a new one to update, we only update the provider list, we do not change the uid.

argzdev avatar May 04 '23 08:05 argzdev

That are really great news! May I ask, does this solve both of the problems, meantioned in the discussion? And are affected user accounts "self healing"?

  1. "FirebaseUser.isAnonymous returns false, even though the account is only registered using anon registration."
  2. Duplicated entries in provider list: "...user is roughly signed up 130 thousand times(!) with anon provider." (https://github.com/firebase/firebase-android-sdk/issues/3885#issuecomment-1446540815)

Thank you, @argzdev

SteveBurkert avatar May 04 '23 09:05 SteveBurkert

Hi @SteveBurkert, the fix is only for the first issue.

"FirebaseUser.isAnonymous returns false, even though the account is only registered using anon registration."

As for the second issue with 130 providers duplicated, this is not yet fixed. We aren't exactly sure what causes the duplicate providers, we're still trying to figure this out, so we can't guarantee that the fix for the first issue also affects the second issue.

argzdev avatar May 08 '23 09:05 argzdev