Firebaseui-android: Anonymous user and account conversion to permanent account

Created on 19 May 2016  ยท  99Comments  ยท  Source: firebase/FirebaseUI-Android

Hi there

Is there a way in which AuthUI supports the conversion of a anonymous account to a permanent one (one of the supplied auth providers)
As stated in the documentation and as I understand it, one must use the linkWithCredential method instead of the "normal" signInWith flow.

Can this be done with the current AuthUI version somehow?

auth feature request

Most helpful comment

Facing similar case. My app uses default anonymous login to provide a frictionless experience while being able to store user data. Log-in (non anonymously) is a secondary optional feature if the user wants to save his data between devices or access specific features. So my app doesn't really have a true logout status and instead rely on anon/non-anon statuses.

1 - Simple scenario;
An anonymous user with some local data decides to sign up with X provider (new account). In this case FirebaseUI creates a new user instead of linking the new credentials to the current anonymous user. Moreover the new user is loged in without a warning, so the anon user is lost for ever. ยฟWouldn't be a good default behaviour to try to link the credentials first? In case an account for those credentials already exists linkWithCredential will return an exception which can be handled as follow (see scenario 2)

2 - Complex scenario:
An anonymous user tries to sign in (to an existing account). In this scenario we have 2 users and I guess it should be managed by the developer. So some sort of callback/result.

I see how this is problematic due to the potential complexity of second case, but at least the 1st case can be solved very easily by trying to link 1st new credentials when an anonymous user is in.

All 99 comments

Hi Daniel,

We don't have any direct support in FirebaseUI for upgrading anonymous accounts to an account backed by an authentication method just yet. You can do it directly with the Firebase Auth APIs, see here in the section "Convert an anonymous account to a permanent account".

I'll leave this issue open as a feature enhancement request; we can likely handle this automatically within FirebaseUI if an existing anonymous user is present in auth.getCurrentUser() when sign in occurs during the authentication flow.

Hi @iainmcgin - thanks for your reply (and considering it as a feature enhancement :) )

I've read through the documentation you mentioned - maybe you can answer me this: Is there a way to get the AuthCredential object from AuthUI which was used by the user to sign-in? As I'm reading the docs, without this object instantiated with the users credentials there's not way to link accounts...

Ah, we have a slight problem here after discussing this with some colleagues. There is deliberately no way to get back an AuthCredential from an existing user, to avoid potentially leaking credentials. So, the linking of the authentication method to the anonymous account would _have_ to occur within Firebase UI if you are using the library. I'll take a look at this next week to get you unblocked ASAP.

Great - looking forward to hearing from you ๐Ÿ‘ (And thanks a lot!)

Facing similar case. My app uses default anonymous login to provide a frictionless experience while being able to store user data. Log-in (non anonymously) is a secondary optional feature if the user wants to save his data between devices or access specific features. So my app doesn't really have a true logout status and instead rely on anon/non-anon statuses.

1 - Simple scenario;
An anonymous user with some local data decides to sign up with X provider (new account). In this case FirebaseUI creates a new user instead of linking the new credentials to the current anonymous user. Moreover the new user is loged in without a warning, so the anon user is lost for ever. ยฟWouldn't be a good default behaviour to try to link the credentials first? In case an account for those credentials already exists linkWithCredential will return an exception which can be handled as follow (see scenario 2)

2 - Complex scenario:
An anonymous user tries to sign in (to an existing account). In this scenario we have 2 users and I guess it should be managed by the developer. So some sort of callback/result.

I see how this is problematic due to the potential complexity of second case, but at least the 1st case can be solved very easily by trying to link 1st new credentials when an anonymous user is in.

@iainmcgin Any updates on this? I'm blocked on the same issue and wondering if I will need to fork FirebaseUI to proceed...

Same issue here ! Waiting for a fix...
In fact, I'm using firebase oauth for a web app, so I'll post in the relevant github project.

I'm quite keen on this feature too.

Looking forward for this enhancement
on both Android and iOS

Has this feature been implemented in the 0.5.1 release? I'm also really looking forward for this.

@shalama this has not been implemented yet. It's something we are looking into for future releases though.

You can see exactly what changes with each release by visiting the release notes:
https://github.com/firebase/FirebaseUI-Android/releases

+1 for the feature request

Hi @iainmcgin ๏ผŒ we have the same feature requirement. Does team have any workaround for this? Hope you can have a response :)

@bobshao Unfortunately, there aren't any workarounds as mentioned above, but I'm going to be able to work on #309 again so I'm hoping for it to cut FirebaseUI v1.1.0 which will be coming out soon.

Thanks very much for your quick response :) @SUPERCILEX

It looks like 1.1.0 is shaping up at #510. Will #309 be included in the version?

@curiousily this will not be making it into the 1.1.0 release due to cross-platform issues. We don't have an implementation on iOS yet.

@samtstern thanks for the info. Will love to hear any plans concerning when this will be merged.

Any progress on this? This feature would be great for an app I'm working on.

@percula #309 is ready to be merged, but it's waiting on https://github.com/firebase/FirebaseUI-iOS/issues/139.

Thanks @SUPERCILEX ! Looks like there's good activity on the iOS side now.

Beginner question: I want to use your implementation of Anonymous Auth linking, so I cloned your fork. Then I followed the instructions to install the repository to Maven Local ./gradlew :library:prepareArtifacts :library:publishAllToMavenLocal. Now, how do I add your fork as a dependency in Gradle and make sure that I'm getting the fork and not the master FirebaseUI? I currently have: compile 'com.firebaseui:firebase-ui-auth:1.1.1', but don't think that's right.

@percula if you change the version number in constants.gradle and then install locally again you can be sure you have the local version.

@percula @samtstern Whoa whoa whoa, you guys aren't being lazy enough! ๐Ÿ˜„ I would just use JitPack if I were you. Once you follow step one on their website (add the maven reop), you can just add my fork like so:

compile 'com.github.SUPERCILEX.FirebaseUI-Android:firebase-ui-auth:d1df8d2c0aef03f3db008d4021059ba316386c7c'

That's way easier to start and to keep yourself up to date with my fork. Cheers! ๐ŸŽ‰

@SUPERCILEX I can't believe Jitpack works with our crazy build system. Very impressive.

@samtstern Yeah, it took a while for me to figure out, but it would have been too much of a pain to re-compile everything every time I wanted to test it in my own app. Basically because we have several modules, instead of com.github.SUPERCILEX:FirebaseUI-Android:version we have to use com.github.SUPERCILEX.FirebaseUI-Android:firebase-ui:version.

@SUPERCILEX Thanks! Works like a charm

Hello!
I have try to add dependency as shown below
compile 'com.github.SUPERCILEX.FirebaseUI-Android:firebase-ui-auth:3a8a53dd5a9709a5cdd6c1e721f0adf07d18478e'
but it conflicts with another dependency
compile 'com.google.android.gms:play-services-gcm:10.2.0'
What dependency I must add to avoid conflicting with play services 10.2.0?

@zzsdeo Here's the updated commit hash with the new dependencies:

compile 'com.github.SUPERCILEX.FirebaseUI-Android:firebase-ui-auth:d1df8d2c0aef03f3db008d4021059ba316386c7c'

@SUPERCILEX thanks a lot!

@SUPERCILEX can you give the commit hash with account linking support in version 2.0.1

Just wondering, how do you generate the commit hash? Thanks!

If we can't present as an option an "anonymous" or "skip login" option using FirebaseUI library why is not anonymous account linking enabled?

As far as I can understand #309 PR code is ready and approved but not merged due to an "ios feature parity" requirement which IMHO is nonsense, whether to use a feature available or not in various platforms should be a technical decision taken by us as developers or CTO's.

Despite of that could you please @SUPERCILEX clarify if anonymous account linking is already implemented in 2.0.1 version? if not, from which alternative jitpack repo is it available? (if it is)

Thanks in advance

@zzsdeo @percula @pamartineza I haven't had time to support phone auth yet so you can use it, but there won't be any linking going on unfortunately, sorry. I have my TODO list for auth (https://github.com/SUPERCILEX/Robot-Scouter/issues/136), but I'll also try to remember to update this issue whenever I have the time to get linking working properly with phone auth. As for the 2.0 commit hash, here you go:

implementation "com.github.SUPERCILEX.FirebaseUI-Android:firebase-ui-auth:ce8478d641"

@pamartineza just to address one additional point you made, I don't think we'd ever add an "Anonymous" option to FirebaseUI in the auth method picker screen. Anonymous authentication should happen without user interaction.

Oh yeah, forgot to mention that. Thanks for the clarification @samtstern

@pamartineza The ideal flow IMO looks something like this:

  1. On app boot if the user isn't signed in, sign them in anonymously
  2. Show them some tutorial where they started creating content immediately to experience the worth of your app
  3. At some point, show them a message like "Wassup bro, wanna create an account to save your data?" ๐Ÿ˜† That's where you use FirebaseUI to link the anonymous account data to the one they create in the sign-in flow.

Hope that clears things up even further! ๐Ÿ˜„

Thanks for your prompt answers, @samtstern , @SUPERCILEX

@SUPERCILEX this is also our ideal flow, but it is paradoxical that official answer is, "yes, you have to sign in anonymously your users on your own, we won't support that option in FirebaseUI, but if you do that, you won't be able to use FirebaseUI because we don't support anonymous account linking"

I have addressed this issue many times to Firebase representatives at Google Campus in Madrid (Spain) since Firebase was acquired by Google and just last June 21th in a Firebase event they assured me that this was already solved, now I'm again confused to see that it is not...

@SUPERCILEX I have successfully linked anonymous accounts to Gmail accounts using your fork but photoUrl and displayName are null and empty respectively, is this an expected behaviour or an issue related with #729 ?

Aha! @pamartineza Thanks for help me figure it out, yeah it does relate to that issue: https://github.com/firebase/FirebaseUI-Android/issues/729#issuecomment-310677279.

Hello, is there an update on when we can expect this to be released? This has been open for over a year now. It would be cool to at least have some realistic expectation of when it will be released. Thanks.

@pamartineza Aw crud! ๐Ÿ˜ข Turns out the null photo and name is expected behavior according to the support agent I contacted:

Hi Alexandre,

Hope you're having a great day. I appreciate your time and effort on voicing this out.

I have talked to our engineer and he confirmed that this is an intended behavior. A good explanation for that is, if the user_id already exists in Firebase Auth, user attributes from Identity Provider (Google, Facebook etc.) are not populated as top level attributes. Instead, they are updated in sub-level user.providerUserInfo.

Let me know if you have any other Firebase-related issues/questions. Thank you for using Firebase and I wish you all the best in your project. :)

Cheers,
Hazel

I'll have to update my PR to do some sort of merge based profile update i.e. if the previous user's name is null but the new one isn't, update the name and etc. for the profile photo. So anyway, I've added it to my growing TODO list.

Hi,
Looking for the same behavior. I think we want to use firebase database in any cases: before user logged in and after too.
I thought about one solution : as soon as user tap on signIn button, before launching firebaseUI, save datas in memory. Then after signIn, if succeed, fetch memory in firebase new user DB space.
But your fork seems nicer solution.

@SUPERCILEX

what we are doing in our current implementation is getting them on the activity on result and then after linking successfully the Gmail account requesting a ProfileUpdate

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {


        if (requestCode == RC_SIGN_IN) {

            val result = Auth.GoogleSignInApi.getSignInResultFromIntent(data)

            if (result.isSuccess && result.signInAccount != null) {
                Logx.d("successful Google sign in " + result.status.statusMessage + " " + result.signInAccount?.idToken + " " + result.signInAccount?.photoUrl)

                mainPresenter.onUserSelectedGmailAccount(result.signInAccount?.idToken as String, result.signInAccount?.photoUrl.toString(), result.signInAccount?.displayName)

            } else {
                Logx.d("failed Google sign in " + result.status.statusMessage)
                mainPresenter.onGoogleSignInFailed()
            }

        } else {
            super.onActivityResult(requestCode, resultCode, data)
        }
    }
fun updateProfileWithDisplayNameAndPhotoUrlCompletable(displayName: String?, photoUrl: String?): Completable {

        return Completable.create { emitter ->

            val builder = UserProfileChangeRequest.Builder()

            if (null != photoUrl) {
                builder.setPhotoUri(Uri.parse(photoUrl))
            }

            builder.setDisplayName(displayName)

            val currentUser = auth.currentUser

            if (null != currentUser) {

                currentUser.updateProfile(builder.build()).addOnCompleteListener { task ->

                    if (task.isSuccessful) {
                        Logx.d("profile updated successfully")
                        emitter.onComplete()
                    } else {
                        Logx.e("profile update failed", task.exception)
                        val exception = task.exception

                        if (null != exception) {
                            emitter.onError(exception)
                        } else {
                            emitter.onError(UnknownError("Unknown update profile error"))
                        }
                    }
                }
            } else {
                emitter.onError(IllegalStateException("currentUser is null"))
            }


        }.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())

    }

Hello again, is there an update on when we can expect this to be released? What is the current status? Is this the wrong place to ask this question? If not, can someone point me somewhere where I can get a response? thanks.

@drod744 we don't offer release timelines, if you read the thread you'll see this will only be released when there is a similar feature on FirebaseUI-iOS.

You can use it now by checking out the branch and building the library locally.

Hello everyone, I have a new release ready to share with you guys! ๐Ÿ˜„๐Ÿš€

Some highlights:

  • Includes the latest release of FirebaseUI (v2.1.0)
  • Full support for phone auth
  • Automatic profile merging so you can, for example, start with an anonymous account and then upgrade to an idp with all the proper user fields being populated such as display name and photo url
  • And last but not least, this release includes #808 which simplifies Twitter auth among other things

Add the following to your build.gradle file to get this release:

implementation "com.github.SUPERCILEX.FirebaseUI-Android:firebase-ui-auth:164165ed92"

@SUPERCILEX Thanks for all the hard work on this. The flow that you've described (automatically logging in anonymously, merging data after logging in) is exactly what I'm looking for.

Can you explain what I need to do to enable "automatic profile merging" so that the logged in user can access their data from the anon state? The word "automatic" implies it should just work once I swap out the standard FirebaseUI with yours in build.gradle, but I think I'm missing something.

My flow is:

  • Open app, the user is automatically signed in anonymously
  • The user creates some data and they have read/write access to it, and I see it populating in Firebase DB
  • Then they log in with, say, Google
  • The data they created while signed in anonymously is now no longer accessible to them... they can only access new data they create (this is where I want to merge!)
  • In Firebase DB, I see their two user IDs, each with separate data

Thanks again.

@harvitronix Glad to hear this PR is useful! ๐Ÿ˜„

You've almost got it, but you're confusing a few things. The "automatic profile merging" I was talking about is literally the user profile so FirebaseUser#getName() and FirebaseUser#getPhotoUrl(). That all happens automatically meaning when the user signs in with a real account (e.g. Google) and their name or photoUrl is null, we add the login profile metadata to their account. You can see the merging in action in ProfileMerger.java.

What you're looking for is database merging and some pretty extensive work has gone into getting that right. However, you are correct in that we can't automagically do all the merging for you. In most cases, it Just Worksโ„ข๏ธ and we can simply upgrade the account type, but if a few rare cases you'll have to manually perform a database merge. I've got some detailed docs that should help you with that: https://github.com/SUPERCILEX/FirebaseUI-Android/tree/master/auth/README.md#handling-account-link-failures.

Just in case this wasn't clear, you do have to manually set a builder flag to enable account linking because of the aforementioned caveat: scroll to the bottom of the sign-in examples or just above handling responses.

Feel free to ask me any more questions! ๐Ÿ˜„

@SUPERCILEX Ah ha! Thank you for clarifying for me. I added setIsAccountLinkingEnabled(true) to the builder and voila, works like a charm. Can't thank you enough for all the work you've done!

How would you handle this scenario:

  • User opens app, is signed in anonymously
  • Writes some data
  • Signs in with Google, IDs merged (woot!)
  • Signs _out_, is assigned a new anonymous ID
  • Signs back into their account with Google

Since the second time is not a new account creation, there's no merge, and so the user no longer has access to their anonymously-created data in the DB. I tried to use the Handling account link failures example as my guide, but when I attempt to get the data the user collected while anonymous, the DB gives me a permission denied.

My DB rules are...

...
"$uid": {
    ".read": "auth.uid === $uid",
    ".write": "auth.uid === $uid",
}
...

And my structure is like...

"users": {
    "uid1": {...},
    "uid2": {...},
}

@harvitronix Sorry for the late reply, I didn't see that you edited your comment! ๐Ÿ˜•

New response:

While writing my response, you gave my a brilliant idea! ๐Ÿ˜„ I found a bunch of bugs in Google's new Play Billing library most of which stem from various memory leaks because they're storing a listener that can include a context:

// Create a result receiver that will propagate the result from InvisibleActivity
// into PurchasesUpdatedListener specified in the constructor.
ResultReceiver purchaseResultReceiver =
  new ResultReceiver(mUiThreadHandler) {
    @Override
    protected void onReceiveResult(int responseCode, Bundle resultData) {
      List<Purchase> purchases =
          (resultData == null) ? null : BillingHelper.extractPurchases(resultData);
      mBroadcastManager.getListener().onPurchasesUpdated(responseCode, purchases);
    }
  };
// Launching an invisible activity that will handle the purchase result
Intent intent = new Intent(activity, ProxyBillingActivity.class);
intent.putExtra(RECEIVER_EXTRA, purchaseResultReceiver);
intent.putExtra(RESPONSE_BUY_INTENT, buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT));
// We need an activity reference here to avoid using FLAG_ACTIVITY_NEW_TASK.
// But we don't want to keep a reference to it inside the field to avoid memory leaks.
// Plus all the other methods need just a Context reference, so could be used from the
// Service or Application.
activity.startActivity(intent);

Anyway, that made me realize that you _can_ pass around callbacks inside intents! I don't have time to work on this right now, but basically this will let me pass around a callback to tell the dev when they should transfer their data and then finish the sign-in. There shouldn't be any memory leaks because you shouldn't need an Activity specific context to transfer your user data.

TL;DR:
I'm going to work on a fix sometime next week that will hopefully solve your problem! ๐Ÿ˜„

Old response:

What I really wanted with this PR was a way to send a callback in that no man's land state between the old account and the new one. However, that kind of refactor would be an in incredible pain in the rear so I haven't done it. Instead, I have two ideas that could work.

  1. This one wastes a bunch of data and time depending on how your db is organized. Basically, anytime the user is preparing to sign in you download all the data you need to transfer and keep it in memory somewhere and then use it if there's a prevUid.
  2. I have another less sucky solution, but it decreases the safety of your user data for a brief period of time. Basically anytime you start a login you set a field like isSigningIn to true and your read rule becomes ".read": "auth.uid === $uid || data.child("isSigningIn").val() === true. For the sign-in duration, anyone could access the data, but it makes your life easier.

@harvitronix Ok, I have some good news and some bad news. The bad stuff first:
Turns out if your process dies the ResultReceiver also dies and I loose your code which kills that idea.

The good news!
I took things to an extreme and we now have a custom service that handles data transfer. The issue with adding a service is that it significantly increases complexity, but at least it works.

I'm going to do some more testing and thinking about how I can improve my code (because it's _gross_) so I'll probably release the new stuff later this week.

For now, here are the security rules I'm testing with:

{
  "rules": {
    ".read": "false",
    ".write": "false",
    "chatIndices": {
      "$uid": {
        ".read": "auth.uid === $uid",
        ".write": "auth.uid === $uid"
      }
    },
    "chats": {
       "$key": {
          ".read": "root.child('chatIndices').child(auth.uid).child($key).exists()",
          ".write": "root.child('chatIndices').child(auth.uid).child($key).exists()"
       }
    }
  }
}

And here's the transfer code:

public class MergerService extends ManualMergeService {
    private Iterable<DataSnapshot> mChatKeys;

    @Override
    public Task<Void> onLoadData() {
        final TaskCompletionSource<Void> loadTask = new TaskCompletionSource<>();
        FirebaseDatabase.getInstance()
                .getReference()
                .child("chatIndices")
                .child(FirebaseAuth.getInstance().getCurrentUser().getUid())
                .addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot snapshot) {
                        mChatKeys = snapshot.getChildren();
                        loadTask.setResult(null);
                    }

                    @Override
                    public void onCancelled(DatabaseError error) {
                        Log.e("TAG", "message", error.toException());
                    }
                });
        return loadTask.getTask();
    }

    @Override
    public Task<Void> onTransferData(IdpResponse response) {
        String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
        DatabaseReference chatIndices = FirebaseDatabase.getInstance()
                .getReference()
                .child("chatIndices")
                .child(uid);
        for (DataSnapshot snapshot : mChatKeys) {
            chatIndices.child(snapshot.getKey()).setValue(true);
            DatabaseReference chat = FirebaseDatabase.getInstance()
                    .getReference()
                    .child("chats")
                    .child(snapshot.getKey());
            chat.child("uid").setValue(uid);
            chat.child("name").setValue("User " + uid.substring(0, 6));
        }
        return null;
    }
}

@harvitronix and everyone on this thread, I'm trying to figure out the right API design so what do you guys think of the Java code above? @samtstern if you have time, what do you think?

@SUPERCILEX thanks for your work on the merging accounts, just what I needed.

Forgive my ignorance, but how do I confirm your alterations on this fork?

implementation "com.github.SUPERCILEX.FirebaseUI-Android:firebase-ui-auth:164165ed92"

I'm trying to work out why my application isn't obfuscating and I want to make sure that this commit is applied to it?

Thanks in advance

Sorry for the delay, I've been busy with #962. Here is the latest and greatest with FirebaseUI v3.1.1 bundled in:

implementation "com.github.SUPERCILEX.FirebaseUI-Android:firebase-ui-auth:52df7658de"

The current implementation works fine AFAIK, but I'm going to stop supporting it until #962 goes through at which point I'll have to rewrite this PR anyway.

Thanks @SUPERCILEX

I hope they're paying you well!

@brandall76 Lol, nah... I reap the benefits in what I learn working on FirebaseUI and other cool perks like alpha access to Firebase SDKs. Oh, and it eventually improves my apps too. ๐Ÿ˜†

Anyway, glad this PR is helpful to you! One of these days it'll be merged. ๐Ÿ‘

@SUPERCILEX well I hope they're going to send you a huge collection of test devices as a sign of appreciation! - You seem like a pretty valuable asset to me, looking around the PRs on here...

I could really do with your expertise on a project I've just pushed!

Anyway, thanks again ๐Ÿ‘

@SUPERCILEX
First off, thank you for your dedication to this project. Your willingness to provide intermediate builds while we're all waiting on that pesky iOS work to be finished is outstanding.

I wanted to mention something real quick.
From my testing earlier today, if you have an anonymous type FirebaseUser (via firebaseAuth.signInAnonymously()), and you later call linkWithCredential(), then the two user accounts should be automatically merged for you. And no manual data merging should be necessary.
From what I remember, it also removed the anonymous account for me after it was merged.
At least, this is what I experienced earlier when merging an anonymous user into a Google user account.

So my question is, why did you go the route of providing an abstract Service that requires devs to manually merge their data if the linkWithCredential() function should properly merge the users (and data) for us?

This is what worked for me (no manual data merging):

private fun linkAnonWithGoogleAccount(account: GoogleSignInAccount) {
        val credential = GoogleAuthProvider.getCredential(account.idToken, null)
        firebaseAuth.currentUser?.linkWithCredential(credential)?.addOnSuccessListener {
            Timber.d("User converted from anonymous to real user")
            // Do other UI stuff here to show user that they were signed in
        }?.addOnFailureListener {
            onGoogleSignInFailed()
        }
}

sorry for the Kotlin ^^

@TylerMcCraw Ah, I see you've gotten into the meat of account linking. ๐Ÿ˜† Since this is a complicated topic, I actually wrote a full section on it in the PR's README.

PS: No problem about the Kotlin, I've got one of my projects to just under 90% Kotlin! ๐Ÿ˜„

So, I read your updated README and drilled through the code. It seems that the Merge Service is _just_ for when you know the anonymous account has data and an existing non-anon account has data.

I was trying to tackle the issue of when a new anonymous account wants to authenticate with a provider for the first time.
I can happily say that this is working for me when using this build on your fork:
implementation "com.github.SUPERCILEX.FirebaseUI-Android:firebase-ui-auth:f8634227b9"
๐Ÿ‘

It's unfortunate that the main FirebaseUI library doesn't support at least this flow already.

Yep, the merging process is only for that rare case where the user already has an account. Glad it's working for you! ๐Ÿ‘

Sorry, one more thing.

There may be an issue or maybe I'm not understanding something.
In the scenario of:

  1. Sign in user anonymously
  2. Link with provider authenticated account (this combines anon and provider accounts and anon account is deleted)
  3. Sign out
  4. Sign in user anonymously (w/ diff anon account this time)
  5. Link with same provider as before

The second anonymous account is not deleted as it was in step 2.
Is this intended? I would imagine that the accounts are combined and the anonymous account is deleted again.
Maybe this is because the provider authenticated account already had an anonymous account linked to it? Are developers required to unlink accounts in this scenario?

Thanks for the help.

Yes, this is expected behavior. When linkWithCredential is used, it really does link the two accountsโ€”that is, the credential is associated with a Firebase controlled user. Thus, the anonymous account isn't deleted at all. Instead the new provider's credential is added to the Firebase user's stable ID and "linked." However, when a collision occurs, Firebase can't link the accounts because there are now two stable IDs which can't be combined. Hence the need for a data transfer service: we have to drop one account and migrate all data to the account we're going to use. For safety's sake, we don't delete the old account when this happens though we could.

TLDR: you have to shift your mindset from a Firebase user being a set of providers to it being a stable ID that can't be changed or merged which is associated with various credentials. When you have two different stable IDs (or users), they cannot be merged but credentials can still be added to them.

I hope this helps!

@SUPERCILEX @TylerMcCraw just to throw my two cents in - I think there are a number of use cases here - some common, some edge.

Given that FirebaseUI attempts to provide a 'plug and play' solution for developers of all experience, I believe there needs to be multiple APIs exposed for both simple and complex merging.

I'm fairly sure the most common use case is where a user is 'upgraded' from anonymous to authenticated. There is no existing authenticated account and therefore the anonymous data is simply transferred and the anonymous account subsequently deleted.

For the above scenario, I think a one-liner

setAccountMergingEnabledAndThenDeleteAnonymous(true)

should be exposed and handled for developers. Obviously with a better naming convention!

Things become a little more complex when there is an existing authenticated account. A user with multiple devices is an obvious scenario. Device A originally creates the authenticated account. Device B & C (etc) need a merging strategy.

I think (although I'm not certain) that many developers may simply want device B & C,D,E... to reflect the existing authenticated data and merging specific data would not be an issue

setAccountMergingEnabledPersistAuthenticatedDataIfExistsAndThenDeleteAnonymous(true)

Again, naming convention caveat!

The remaining scenario is the requirement for a merging strategy. Consider the following simple examples:

  • The current stage of a game needs to persist with the highest attained by all devices reflected as the final authenticated state. At the point anonymous device B authenticates at stage 8, if this is higher than the current authenticated state of device A (which was only stage 2), the stage value is overwritten.

  • The number of gold coins earned on each device needs to be merged as collective total as each authenticates.

I wonder if a generic 'merging strategy' should be considered and exposed, so developers can reuse common rules on defined fields, such as overwrite, append, add, subtract etc. If their merging strategy is more complex, then naturally they will have to manipulate their data accordingly.

In summary, I think account merging is essential and an extremely common requirement. My input would be, that many developers will start to sweat when they see that it's not a one liner for their trivial requirements and they need to implement a Service to achieve this.

I understand that a Service may become an integral part of FirebaseUI if #962 goes through, but I'm pretty sure that if it does, it will be embedded in the library rather than exposed in a way where a user will need to extend it.

Two cents complete! The main thing to take from the above @SUPERCILEX is that the work you have done here is amazing and I'm extremely grateful for it.

Well dang, those are some long two cents! Maybe more like three or four? ๐Ÿ˜‚

Ok, anyway... First off, thanks for all your input!

For the simple, snap your fingers case of linking accounts, I'm hoping to get that into FirebaseUI by the end of the year with a big refactor we're doing (no promises, of course ๐Ÿ˜„). As for the other cases, here's my philosophy: devs should be forced to think about this to ensure a good user experience. You suggested a case where the user's data isn't transfered, but this could lead to devs permanently losing anonymous account data. Unless you store the uid somewhere permanent, it will be impossible to identify an anonymous account once the user had been signed out from it. Thus, my reasoning was that for good UX devs need to think about this. (Though you could implement a service that just returns Tasks.forResult(null) and call it a dayโ€”highly unrecommended).

As for why we need to use a service, FirebaseUI has no knowledge of how your database is organised or even what database you're using. The service provides a guided transfer that enables custom code execution during the sign-in. Thus, devs just have to implement a load and transfer method which isn't too complicated. The other reason you can't do it in onActivityResult is because it's too late by then in terms of security rules. If you lock access to data for only the signed in user, security rules will correctly assume you're a different user, hence the need to inject a load and transfer into the sign in process.

I hope this clears things up! ๐Ÿ˜„

@SUPERCILEX Hi, is there a new version of your anonymous auth fork for FUI 3.1.1?

@krisu7 Yep, I've tested ab184fde51 and updated this comment with the latest install instructions.

@SUPERCILEX thank you

Hi @SUPERCILEX, thank you for your effort!

FUI 3.1.2 has been just released and it fixes a bug for transitive dependencies and also upgrades to support library 27.0.2 [1]. Do you have any plans to update your fork or will your development get merged to mainstream anytime soon? Thank you in advance.

[1] https://github.com/firebase/FirebaseUI-Android/releases

@txedo Dunzos: fa824262ab

Hey @SUPERCILEX

First of all, thank you very much for all your efforts๐Ÿ™Œ.

So, I have been in one of those rare cases where the user already has an account. I am creating the intent with setIsAccountLinkingEnabled(true, MyManualMergeService.class) to start the sign in flow, the problem is, I get an error saying _This credential is already associated with a different user account_ when the user clicks verify phone number. This should happen if one tries to link an account using firebase's linkWithCredential because the credentials are already linked to another user account as mentioned in the docs, but, as per understanding, after reading your README and going through this thread, you have handled that case in your fork.

I am not able to figure out a reason or solution. Please help.

PS: I am having multiple apps under the same firebase project and they share the database and authentication from the project. Now, in my case, the previous user account is from app A that requires registration to use it but there is this another app B that does not require user login as long as user does not want to perform some specific activities, at which point, user has to sign in. I implemented anonymous login for this app B. I want the user to be able to login into app B with the credentials associated with app A and vice versa. Surprisingly, the vice versa case, that is, first logging in app B and then using same credentials in app A is working fine.

@kkdevenda Apologies for the delay, and thanks for the detailed investigation! I'll investigate this issue soon. ๐Ÿ˜‰

Hey @SUPERCILEX

Our team is planning to use your fork (thus use setIsAccountLinkingEnabled()). We want to simplify things a bit. When linking an anonymous user to a registered one we would like to:

  • When the user has no previous account - simply link his account and retain the data
  • When the user has previous account - delete the user current data and transfer the data from the registered account

We are using the Firestore database. Do you foresee any problems that we might encounter during the implementation?

Still can't believe how core developers do not prioritize implementing this feature since it is considered best practice. and recommended by Google.

Thank you for the huge effort!

@kkdevenda my B, you're right. This is fixed in 6a56930af6! ๐ŸŽ‰

@curiousily What you described is exactly what occurs ๐Ÿ‘, you'll just have to use the manual merge service as described here.

PS: no promises, but the Firebase team is aware of this oversight and is working to fix it. ๐Ÿ˜‰

Hi Alex @SUPERCILEX , can you help. i have a question on how i can delete the anonymous account after logging in with a real google account for example. The situation is user was logged in anonymously and then logged in WITH AN EXISTING ACCOUNT. the accounts cannot be linked since its an existing account. so merging must be done manually. and given your example on the mergeService i dont see how i can delete the anonymous account from the firebase console ? i would need the credential right ? can you give an example how this would be done in your mergeService ? for me i dont have any data to merge, i'd just like to remove the anonymous user from firebase as they are now logged in with a proper account.

@j2emanue You can't and this is acceptable behavior. I agree that it triggers our internal developer's need for cleanliness, but there's no nice way to ensure the account has been fully migrated and also delete the account. (By the time the migration is done, it's too late to delete the account.) Sorry for not being able to provide a better answer! ๐Ÿ˜Š

What I'm doing at the moment to sort this out is to use firebase functions to delete the anonymous user, and copy/delete(Anonymous) its current data in case of a new sign in or just delete the anonymous user data in case of concurrent signin.
I have a property "anonymous_user_uid" in my user data which triggers a function, after the "Write" function has finished it deletes the "anonymous_user_uid" related to that uid.

Thanks for sharing, that's a cool idea! Not something we can do in the library, but good to know as individual developers.

@GuanacoDevs thank you for the feedback. can you show code how you are deleting an account based on anonymous_user_uid only ? i see to delete an account i call currentUser.delete(). i dont see how your getting the anonymous user by uid ? your not even logged in anymore as the anonymous user so how can the account be deleted ? although with the admin sdk i see a way: FirebaseAuth.getInstance().deleteUserAsync(uid).get();

@j2emanue Hey man, I meant Google Cloud Functions, not within your App, using GCF you can delete any user even if is not signed In. Delete User.
Now what I do with those functions is to set different triggers, that will execute them in case:
New User Signs In or Returning User already has data.
My app always signs them Anonymously.
1- When they choose to sign in I backup their Anonymous data under a node which is Readable/Writable by everyone as long as they are Authenticated, I set a flag under that node that triggers a copyAnonymousUser GCF and I save in sharedPreferences() the anonymousUid before launching FirebaseUIAuth.
2- If for some reason they don't sign in I delete their backup in onActivityResult()
3- If the signs In is completed I check if is a returning user, if so I just delete the Anonymous User data and account(I should update in case that used the app while anonymous, but I'm waiting for the silent sign in @SUPERCILEX ), I use the anonymousUid saved before to trigger a function so that it will know what data and user to delete.
4- If is a new user I use the backed up data to populate its "new" account. After that I clean the "shared" node, for this I trigger another function to execute always from GCF
They just released v1.0
Note that I'm not linking accounts, just copy/paste/delete
Wish I knew how to do this before, now my database is cleaner.
I really suck at Java Script because functions is the only reason I have to use it, so virtually I'm not really sure what I'm doing.

Regards
GuanacoDevs

@GuanacoDevs Silent sign-in is ready on my side, just waiting on @samtstern's approval. ๐Ÿ™ƒ

Hey @SUPERCILEX,

What (stable?) commit hash from your branch would you recommend that targets Play Services 15?

Thanks for your response. Our team already implemented your suggestions!

Cheers!

@curiousily Sweet! I'd go with 38e2ece9bbโ€”it includes 3.4-dev, but that's only db and opt-in auth features (e.g. #1212) + bug fixes so nothing to worry about.

Hey @SUPERCILEX ,

Did this made it to the v4.0.0? If not what would be the last stable commit hash from your branch? Is it the same from Apr 25 38e2ece9bb ?

Thanks a lot!

@GuanacoDevs and where do you merge the data from anonymous user with the new signed in user? We are talking about linking an anonymous account with a user account on the sign in process, no?

@unxavi @GuanacoDevs I think there is some confusion. The new "silent sign-in" feature is not related to this issue at all. Anonymous upgrade has not been shipped.

@unxavi @samtstern Apologies, I read my previous post here and not the title of the issue, got confused.

Hey @SUPERCILEX,

Any new recommended hash that includes the changes in 4.0? Would you recommend a way to select hashes by ourselves, so I don't bother you every now and then?

Not even #1185 made it into 4.0...

Sorry to bother you again. Thanks for the hard work you're putting in!

@unxavi @curiousily no worries about asking for new hashes. ๐Ÿ˜Š (Though if you go to the Commits tab here, you'll find the latest and greatest.) Anyway, c280098444 includes 4.0 plus the account linking stuff. Cheers! ๐Ÿฅ‚

how can I add Authenticate with Firebase Anonymously into FirebaseUI

like this

enter image description here
capture

Although the user can log in anonymously, I need to give the user a different id to identify the user

@mike1128 The point is that it's _anonymous_. ๐Ÿ˜‰ So basically, you just call FirebaseAuth#signInAnonymously() and you're good. There's no UI.

I want them in the same page.

So user can choose sign in with facebook or email or sign in anonymously

from one interface.

@mike1128 This isn't something the user interacts with though. Typically, you'd call signInAnonymously() on app start if the user isn't signed in.

Thank you for your replying.

But it doesn't solve the problem.

If I call signInAnonymously(), I should give user a button.

so it's two interface or page, not one.

what i want it like this

startActivityForResult(
AuthUI.getInstance()
.createSignInIntentBuilder()
.setAvailableProviders(Arrays.asList(
new AuthUI.IdpConfig.EmailBuilder().build(),
new AuthUI.IdpConfig.FacebookBuilder().build(),
_new AuthUI.IdpConfig.AnonymousBuilder().build()))_
.build(),
RC_SIGN_IN);

and then Handling the sign-in response

like
if (requestCode == RC_SIGN_IN)
{
if (resultCode == RESULT_OK)
{
//write uid to firestore

I see what you're saying, but the whole point of signInAnonymously() is that there's no user interaction. So that means no button. You should call the method on app start without the user needing to do anything.

what if user want to sign in on app start?
so I need give the user a choice to sign in with email or sign in with facebook,
or sign in anonymous

Oh, I think I see where you're confused. Anonymous sign-in isn't persisted anywhere. That means if the user re-installs your app one way or another they'll lose all their data. The point of my fork is that you sign in anonymously on app start so you can use the db and all that, then when the user is ready to sign-in after going through tutorials or something, you present them with a reliable sign-in method.

Hi all,

Some basic support for this feature has been released in version 4.1.0. More to come!

I'm going to attempt to clarify and organize what's going on so this post can be used as a starting point for any newcomers.

Current state of affairs

Public releases of FUI include support for non-conflicting upgrades. What does that mean? If a user only has an anonymous account and then signs up for a real account, you're good to go. However, anytime the user is signed in anonymously but already has a real account, you'll run into a merge conflict which can't be resolved nicely. BTW, when I say "real" account, I mean something with a stable ID like an email or phone.

What does the fork do?

Simply put, it lets you resolve merge conflicts by transferring a user's data to their real account with security rules in mind.

How do I get it?

Setup JitPack with this repo: com.github.SUPERCILEX.FirebaseUI-Android:firebase-ui-auth:60f451cfea

Documentation can be found here: https://github.com/SUPERCILEX/FirebaseUI-Android/blob/anonymous-auth/auth/README.md#account-linking

Hi, folk. I found a method called enableAnonymousUsersAutoUpgrade(). It converts the anonymous user to a permanent one.

I don't know when it was added but I think it answers the question.

AuthUI.getInstance()
.createSignInIntentBuilder()
.enableAnonymousUsersAutoUpgrade()
.build()

@SUPERCILEX , can you please comment on this enableAnonymousUsersAutoUpgrade()? Is it comparable to your fork? Thanks!

Edit: Just looked at the documentation. They appear to be similar. The key difference is "When linking is unsuccessful due to user collision, an error with code ErrorCodes.ANONYMOUS_UPGRADE_MERGE_CONFLICT will be returned to onActivityResult()". So instead of passing a merge conflict class, you handle it in onActivityResult()

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghost picture ghost  ยท  5Comments

ozican picture ozican  ยท  6Comments

imax531 picture imax531  ยท  3Comments

joknu1 picture joknu1  ยท  4Comments

Nebneb picture Nebneb  ยท  3Comments