Closed danielkummer closed 4 years ago
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:
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:
v2.1.0
)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:
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:
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! π
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! π
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.
prevUid
.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?
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?