Open willbattel opened 4 years ago
Hey Will, Malcolm from the Firebase team here.
First of all, thank you for the detailed summary of why this is an issue for you - it's so much easier to get on the same page given that you've been so prescriptive with your needs! And before I get any further, I've filed this as an internal feature request at b/162766712.
Now, when I look at the heart of your problem I see a conflict between two notions of identity. You have 'Users' and 'devices' as categories of identity, and sometimes those identities can be bound together. I assume, though you haven't made this explicit, that this is so that you can do some form of RBAC (role-based access control) based either on the device or the user accessing the data. The reason that you're then having problems is that linking accounts isn't used for RBAC - account linking is basically saying "I'd like to add an extra login to this account" not "I'd like to merge these two identities, which are equally privileged". Building RBAC yourself to properly account for having both devices and users as first-class identities would be fairly tricky (you'd have to build a user-relation table and query that before doing any operations, and you might potentially have to build an extra identity layer that can appropriately translate between the identities you're managing); this is a problem that we're interested in exploring internally, so it's good to get more feedback about this.
Now, ignoring the fascinating intricacies of making RBAC work with Firebase Auth, let's just consider this statement you made: "Users can link multiple devices together by signing into a non-anonymous provider". While that's true for the first time you link a non-anonymous provider to an anonymous account, the second time you try that (e.g. when a user tries to log into a second device that already has an anonymous account), you're going to get an error and you'll be required to do manual account merging. Given that that problem exists, I'm back to thinking that you might need to keep device accounts and user accounts separate and manage their relations in a table somewhere.
Given the above, I don't think that this feature request fully solves the set of problems that you'll run into. If I've missed something, please bring it up - I'm happy to figure it out together :) If not, I'd suggest you build a slightly different model for your authentication system. What do you think?
@malcolmdeck Thanks for the detailed insight. Let me respond to a few of your points.
Now, when I look at the heart of your problem I see a conflict between two notions of identity. You have 'Users' and 'devices' as categories of identity, and sometimes those identities can be bound together.
Correct.
I assume, though you haven't made this explicit, that this is so that you can do some form of RBAC (role-based access control) based either on the device or the user accessing the data.
Essentially, yes. We do RBAC using the user
identity. For example, a user might be allowed to perform different actions if they are a free user of a paid subscriber. All RBAC is tied to the user, and not the device.
The reason that you're then having problems is that linking accounts isn't used for RBAC - account linking is basically saying "I'd like to add an extra login to this account" not "I'd like to merge these two identities, which are equally privileged".
Correct. We link accounts in order to provide means to authenticate beyond the original device, as well as to unify user content on multiple devices owned by the same user (see comment below regarding users/devices relationship in Cloud Firestore).
While that's true for the first time you link a non-anonymous provider to an anonymous account, the second time you try that (e.g. when a user tries to log into a second device that already has an anonymous account), you're going to get an error and you'll be required to do manual account merging. Given that that problem exists, I'm back to thinking that you might need to keep device accounts and user accounts separate and manage their relations in a table somewhere.
This is exactly what we do. In Cloud Firestore, we have a users
collection and a devices
collection, with a one-to-many relationship connecting them. We then also have collections for user content that I will represent and collectively refer to as the posts
collection in this issue. In earlier versions of our app, we had only the users
and posts
collections, but eventually we introduced the devices
collection as a means of avoiding user content migration- i.e., when we have to perform an account merge we don't want to have to iterate through posts
associated with the user being merged to reassign the user. Adding the devices
collection allowed us to associate posts
with a device
instead of a user
, and then we associate devices
with users
. When we merge accounts we now need to only change which user
the device
is tied to. This might not normally be the best solution, but for the nature of our particular app it makes sense to treat devices as first-class identities.
Given the above, I don't think that this feature request fully solves the set of problems that you'll run into.
Correct. This feature on its own would not solve problems encountered when attempting to implement this. However, this feature would add some QoL-type value to existing mechanisms. Here are two examples that would benefit us:
When we perform account merging after encountering the linking conflict error, we have to migrate any applicable user data and then sign into the destination account. This process has several points at which failure could occur. Should there be a problem with the merge, be it an error in Cloud Firestore, Cloud Functions, authentication, or anything else involved, we want a mechanism to revert to the starting state. That is, we want to be able to restore the original anonymous credentials. These credentials are instead discarded by the SDK when the signInWithCredential
function resolves. Currently, if we need to "back out" of a problematic merge after the sign in has occurred, we have no choice but to create a new anonymous user. This issue was previously discussed in #5342 where the suggestion arose to use two FIRApp instances- which could solve the problem but I would argue is not ideal. The fallback process would be much much simpler if we could instead just "reactivate" the anonymous credential.
Throughout the app life cycle as users sign in and out of non-anonymous accounts on their devices, we would prefer that the anonymous account not change every time. Retaining the anonymous account instead of them being disposable would make the identity terrain less chaotic. We find value in treating an anonymous user as the "root" identity of a device if no other identities (read: non-anonymous providers) are associated. If a single device will never have had more than one unique anonymous user used to authenticate, then we can call upon said user, even if dormant, to identify the device. Of course we're able to make do as-is, this is instead a "nice to have" type benefit.
Let me know if you think what I've described makes sense. I'd be more than happy to continue a discussion diving deeper into nuances, benefits, challenges, etc...
Thank you for the detailed response :)
I think that there are two things for us to consider
We have this same problem in FirebaseUI where you might need to merge accounts and there's a concern of losing a user credential. The way that we solve it is by first creating a second FIRAuth
instance, and then copying the user state from the existing instance to the new one using -updateCurrentUser
(found here). Then, we can mutate as much as we want on the new instance (for example, trying to link accounts) without losing the existing credential. I know that that's not ideal for you, but it at least solves some of your migration story while we think about (and maybe build/release) your feature request.
First of all, as I said in my first response, I've filed this as an internal feature request as b/162766712 so you'll get your due from the rest of the team independent of the opinions that I express here. I think that the first point you make about not being able to restore credentials to an Auth instance is a reasonable one - for example, if I sign a user in via the Admin SDK it's often not so easy to make the state on the client-side match up appropriately. We don't field a lot of complaints about this right now, but it annoys me anyways, and if you had some way to mint the anonymous user account a new token from the Admin SDK then I imagine you'd be set. (Actually, you might be able to do this - I need to refresh my memory on what happens when you specify an existing UID in a custom token and exchange it using the Admin SDK).
As for anonymous accounts persisting across log outs, that's against the core philosophy we have about what an anonymous account is/isn't for. In our view, anonymous accounts are transient state intended as part of an app onboarding flow. That way, you can have a UID for a user before making them sign up, which is an attractive feature for a variety of developers. Because the UID stays with the account when you link accounts, you can't have anonymous accounts bound to device identity in some way, since that will cause a collision when you have an upgraded (linked) anonymous account and try to mint a new anonymous account on that device.
Also, using device identifiers to keep track of anonymous accounts would be really tricky for us to do from a privacy/legal standpoint; I'm not a lawyer so I don't know the details here, but I would imagine that having anonymous accounts based on something device-specific wouldn't pass muster for some subset of the use cases that we currently cover (i.e. it may require user consent, which anonymous accounts presently do not IIRC). I could certainly be wrong, though - again, I'm not a lawyer.
Another thing that occurred to me is that you could be using Custom Authentication to identify devices in the way that you want. Custom Auth allows you to use the Admin SDK to mint user accounts based on anything you want (you could, for example, use the Firebase Installations ID to keep track of what app instance it is across sign outs, where an app instance is defined as an app on a device for the duration of the time it is installed). Then you get what you want, which is separate identities for devices and users, and both types of identities can be logged into and out of at will. The migration from one to another would be annoying, but you could write a batch job to upgrade the existing anonymous accounts to custom token accounts that should get the job done.
As always, if you have more thoughts or concerns, I'd love to hear them - this is a really interesting problem to try to solve, and your responses have been cogent and pleasant :)
Thanks, ~Malcolm
We have this same problem in FirebaseUI where you might need to merge accounts and there's a concern of losing a user credential. The way that we solve it is by first creating a second FIRAuth instance, and then copying the user state from the existing instance to the new one using -updateCurrentUser (found here). Then, we can mutate as much as we want on the new instance (for example, trying to link accounts) without losing the existing credential. I know that that's not ideal for you, but it at least solves some of your migration story while we think about (and maybe build/release) your feature request.
I didn't realize FUI did this, that's very interesting. I'll take a look to see if we can't do the same. We actually use FUI, but when there is a merge conflict we're on our own to handle it.
As for anonymous accounts persisting across log outs, that's against the core philosophy we have about what an anonymous account is/isn't for. In our view, anonymous accounts are transient state intended as part of an app onboarding flow. That way, you can have a UID for a user before making them sign up, which is an attractive feature for a variety of developers.
I'd like to respectfully challenge this philosophy. I completely agree that the primary value of anonymous authentication is to avoid onboarding friction caused by requiring the user to signup/in. This is the main reason that our app uses anonymous auth, and it does indeed solve that problem perfectly. What I would like to challenge, however, is the idea that anonymous auth has no further purpose beyond onboarding.
Because the UID stays with the account when you link accounts, you can't have anonymous accounts bound to device identity in some way, since that will cause a collision when you have an upgraded (linked) anonymous account and try to mint a new anonymous account on that device.
That is true for users that had their anonymous account upgraded- but not true for users that had to merge into an existing non-anonymous account.
Let's talk about two different configurations that provide different scenarios. In the first configuration we don't upgrade any anonymous accounts, instead we always create a new account with a new UID, etc.... In the second configuration we do upgrade anonymous accounts, and switch users only in the event of merge conflict.
The first configuration is the only one that would (theoretically) allow anonymous accounts to be bound to device identity, assuming we could retain them post-signin. In this configuration, the user initially starts with an anonymous account. Later, the user decides to sign in, and FIRAuth is switched to a new user account with one or more non-anonymous providers. When this happens, we would like for the now-dormant anonymous user/credential to not be discarded. Sometimes our users will decide to sign out of their account on one of their devices. When this happens, we present them with a screen allowing them to either sign into another non-anonymous account, or to continue anonymously as they did when they first installed the app. In the latter case, FIRAuth is currently designed to create a new anonymous account- which isn't catastrophic, but not ideal. In this configuration, I argue that we should be able to reactivate the previous anonymous account. This is part of my challenge to you and the Firebase team: I think anonymous accounts should be able to serve as more than just a transient state for onboarding. Why shouldn't our app be able to use anonymous accounts as a means of allowing users to sign out of an account without signing into a different one? What if the app imposes a limit on number of devices that can be signed into an account, and the user wants to swap one with another? What if the device changes hands? Why can't anonymous accounts provide a more general mechanism of anonymous identity beyond only the onboarding stage? I believe it's a reasonable idea to allow users to return to an anonymous state after having previously signed in. It's not a super common use case, I'll admit, but that shouldn't rule it out as a possibility. On top of that, there are more than a handful of apps that use only anonymous auth for permanent authentication, where they never ask or even allow the user to sign in. Regardless of the current intention of anonymous accounts, I urge the FIRAuth team to consider to consider the added utility that would come with anonymous account retention.
The second configuration is less black and white, as you mentioned. When anonymous accounts are upgraded with a non-anonymous provider, it doesn't make sense to retain the anonymous credential. In this configuration, trying to bind anonymous accounts to device identity doesn't really work- at least not very well. The only times you will have anonymous accounts left over are in cases where merge conflicts occurred and the authenticated user was changed. In this configuration, the only minor grievance we have is that if we later sign in anonymously then a new account will be created unnecessarily. FIRAuth should be able to reactivate the anonymous user if it is present, and create a new one only if there is no anonymous credential found on the client.
The main problem I see with this request is that it adds complexity to an otherwise simple mechanism. I do think my request holds water, but it's less of a simple drop-in and more of a beginning of a discussion about the anonymous account usage and lifecycle.
EDIT: I'll also comment on your idea of custom auth later- I'm short on time at the moment.
As a followup to my previous response, I guess there are really just the two sub-issues that we care about:
Robustness and fallback of user merging: where we need to be able to revert to our starting state in the event of uncorrectable error during data merge/migration. This ought to be achievable currently with the dual FIRApp instances mentioned in #5342 but is less than elegant in my opinion. It's also not documented very well because it's not a common configuration. This leads me to wonder what other Firebase users do to overcome this hurdle. I can't imagine that wanting to safely merge two accounts, with a sense of atomicity, is a rarity- and yet I don't see much discussion or documentation on the matter.
Unnecessary anonymous account creation when the last-used anonymous account on the device wasn't upgraded. Why do we have to create a new anonymous account when we previously had a perfectly valid one? The point of this item is to have more stable UIDs, where even if we can't bind them to device identity, we don't have to create a branch new user for no reason other than "that's just how the SDK works". There isn't a lot of weight to this item, it'd just be a nice-to-have. The first item is more important to us.
Hey Will,
Sorry for the slow reply - I have a few more plates spinning than I can comfortably handle right now. I talked to my team about this today, and the general sentiments that came out of that discussion were:
I know that this doesn't really answer a fair amount of the argument you make above, and I apologize for that. That being said, I figured any news was better than no news, so I figured I'd at least let you know that we had talked about it.
Thanks, ~Malcolm
Hey Malcolm, thanks for the info. There is some discussion happening on our end. I'll provide a full response when able.
Malcolm, after further discussion, there are two things we would like to come from this discussion:
Improvements to documentation regarding dual FIRApp instances in order to more safely perform user merge in the event of conflict during anonymous upgrade. See related discussion here: https://github.com/firebase/firebase-ios-sdk/issues/5342#issuecomment-665146290
A deeper dive into the device identity concept, where one can either "sign in with device identifier" as you mentioned, such that we can ask FIRAuth "What user is currently signed in, and/or what device if applicable" (web clients may not make sense here, but Android and iOS should). Unlike most, our application treats devices as first-class citizens, and we want to be able to associate user generated content with both the user and the device it was generated on. It would be fantastic if FIRAuth could provide a mechanism for device identity in addition to the currently "primary" user identity.
@willbattel were you able to find an elegant solution for that?
Currently I'm the same situation described by @willbattel and I didn't find any reasonable solution.
@willbattel can you point us in the right direction on solving this specific use case?
Feature proposal
Our app does not force users to sign in with a non-anonymous provider when they use the app. Instead, we authenticate them anonymously until they choose to sign in, allowing them to securely interact with Cloud Firestore and Cloud Functions as needed.
After signing into a non-anonymous provider, the user can at any later date choose to sign out. When this happens, the user has two choices: they can either sign into a different non-anonymous account, or they can proceed anonymously as they did originally before the initial non-anonymous sign in.
When anonymous users sign into non-anonymous accounts, the current Firebase SDK discards their anonymous credentials and abandons the account. This probably has little impact in most apps. However, in our app, we would really like it if the FIRAuth module could be configured to instead retain the anonymous credentials, so that the user could sign back into their original anonymous account, instead of needing to create a new anonymous account.
The reason this is important to us is because, in our app, we treat devices as first class citizens- meaning that some user generated content is associated with the device instead of the user. Users can link multiple devices together by signing into a non-anonymous provider- but devices can also stand alone with anonymous authentication. When users occasionally sign out of a non-anonymous provider, we don't want to have to create a new anonymous account. It would be fantastic if the Firebase SDK could retain the dormant anonymous credentials alongside the non-anonymous credentials if present, so that they could be re-activated later if desired.
Perhaps the
signInAnonymously
function could have a parameter added that opts into this functionality, such that if the flag is set to true: the previous anonymous account will be signed back into, and if set to false (default value and current SDK functionality): a new anonymous account will be created and signed into.