Description
[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 FirebaseNetworkException
s.
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.
- Initialize Firebase auth state
- 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)
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)
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.