auth icon indicating copy to clipboard operation
auth copied to clipboard

Anonymous user identity not linking

Open muezz opened this issue 2 years ago • 22 comments

Describe the bug If I sign in using Google, re-install the app, open it (which automatically signs the user in anonymously) and then try to link identity using the Google provider, I get an error.

To Reproduce Steps to reproduce the behavior:

  1. Create a fresh Flutter project and add supabase_flutter as a dependancy.
  2. Run the project and set up a button to performt he following action:
await Supabase.instance.client.auth.signInWithOAuth(
  OAuthProvider.google,
  redirectTo: 'com.my_app://login-callback/',
  authScreenLaunchMode: LaunchMode.externalApplication,
);
  1. After signing up successfully, uninstall the app from the simulator.
  2. Add the following code before runApp():
if (Supabase.instance.client.auth.currentSession == null) {
  await Supabase.instance.client.auth.signInAnonymously();
}
  1. Update the onTap callback on the previous button to trigger the following code:
await Supabase.instance.client.auth.linkIdentity(
  OAuthProvider.google,
  redirectTo: 'com.my_app://login-callback/',
  authScreenLaunchMode: LaunchMode.externalApplication,
);
  1. You will be able to sign in with google successfully but as soon as you get back to the app, you will get this error: AuthException(message: Identity is already linked to another user, statusCode: null)

Expected behavior The anonymous user ID (created on the second app run at step 4) should get merged with the already existing user ID from step 2.

Version: ├── supabase_flutter 2.5.0 │ ├── supabase 2.1.0 │ │ ├── functions_client 2.0.0 │ │ ├── gotrue 2.6.0 │ │ ├── postgrest 2.1.1 │ │ ├── realtime_client 2.0.3 │ │ ├── storage_client 2.0.1

Additional context This also brings up a question about what would happen to the data created by the anonymous user? Ideally, I would expect that data to be "moved" to the previously existing user.

muezz avatar Apr 10 '24 04:04 muezz

I'm going to transfer this issue to our Auth server repo to discuss further.

dshukertjr avatar Apr 10 '24 04:04 dshukertjr

@dshukertjr any update on this issue?

muezz avatar Apr 12 '24 00:04 muezz

@muezz Actually, I was reading through the issue again, and the behavior you outlined is the expected behavior.

You simply cannot have two auth users to be tied to a single Google OAuth account, and that is what the error message is saying. When you get the Identity is already linked to another user error message, what you can do is to just perform the regular Google sign-in using either signInWithIdToken or the signInWithOAuth. Once the sign-in is complete, the user will be brought back to the original account. Note that when this happens, the activities from the second anonymous account will be lost, so you are going to have to set something up manually if you want to persist those data.

dshukertjr avatar Apr 14 '24 13:04 dshukertjr

@dshukertjr

Maybe I misunderstood something, but this breaks the flow. ( repeated login gets weird double redirect UX )

Existing user can always log out and another "anonymous" user be created on the same device, probably automatically for most use cases. This means that when they try to "sign in" again -> we have an option to call either linkIdentity or signInWithOAuth -> but we cannot know which one to call, the current session user is anonymous again.

(I'm using Next.js, server side flows)

hala94 avatar Jun 19 '24 20:06 hala94

Fair enough. Passing the feedback to the auth team.

Let me just re-open this issue.

dshukertjr avatar Jun 20 '24 00:06 dshukertjr

@hala94 I think what @dshukertjr stated is correct and a fine way of going about it. The only change I would make to his recommendation is that after the Identity is already linked to another user message is given to a user, they should be given a sign in button to perform the signInWithOAuth. The reason why it would be bad for linkIdentity to perform the signInWithOAuth internally is that if a user was signed in already via email/password lets say and they now decide they want to linkIdentity to a OAuth provider and it turns out that the OAuth they are trying to link is already linked to another account, they would get signed out of their current session (email/password session) and then signed back into the OAuth session instead which is a completely different account all together. I think with the current implementation, it allows the developer to chose the flow they want to provide for the user of their app.

NB: I'm not a Supabase employee, just to be clear that this is my personal take on this matter.

silentworks avatar Jun 25 '24 09:06 silentworks

Related to this, when linking the user via email, updateUser works when updating email, but throws AuthException(message: Anonymous user cannot update password) when also specifying a password. It would be nice to allow them to sign up with email and update their password.

sapphire008 avatar Jun 30 '24 02:06 sapphire008

@sapphire008 Definitely bumping this, I won't be able to use this feature at all until it allows normal sign up flows with passwords

daveycodez avatar Jul 21 '24 09:07 daveycodez

@muezz Actually, I was reading through the issue again, and the behavior you outlined is the expected behavior.

You simply cannot have two auth users to be tied to a single Google OAuth account, and that is what the error message is saying. When you get the Identity is already linked to another user error message, what you can do is to just perform the regular Google sign-in using either signInWithIdToken or the signInWithOAuth. Once the sign-in is complete, the user will be brought back to the original account. Note that when this happens, the activities from the second anonymous account will be lost, so you are going to have to set something up manually if you want to persist those data.

Hello, I have an issue with linkIdentity. When user is trying to link a social account that has been linked with another account, the user will be forced to logout automatically. Can we have a control on that? Even with a custom server callback, the user will still be forced logout.

What I want to do is to just simply display an error message to the user saying that the account that they are trying to link is already linked.

raymondctc avatar Aug 05 '24 22:08 raymondctc

The only mention to this kind of behavior is here, but there is no "how to" actually solve it. How do I merge the "carts"?

eifr avatar Aug 06 '24 06:08 eifr

There is also a technical issue here that prevents me from "merging the carts" in the first place: As I get redirected to my own page after the oAuth flow, I get the query parameters ?error=server_error&error_code=422&error_description=Identity+is+already+linked+to+another+user. So I should be able to handle the error as described here. But the issue is that at this point the sb-127-auth-token cookie for the anonymous user is gone. So I don't have the data of the anonymous user anymore at this point. So how could I "merge the carts" now?

jonnylangefeld avatar Aug 25 '24 22:08 jonnylangefeld

@sapphire008 @LeakedDave this is possible now since https://github.com/supabase/auth/pull/1739, we're planning to roll it out to the hosted platform soon

kangmingtay avatar Aug 29 '24 18:08 kangmingtay

Hello, I have an issue with linkIdentity. When user is trying to link a social account that has been linked with another account, the user will be forced to logout automatically. Can we have a control on that? Even with a custom server callback, the user will still be forced logout.

@raymondctc i'll look into this but i think we're removing the session if an error is returned by linkIdentity in the JS client lib

kangmingtay avatar Aug 29 '24 18:08 kangmingtay

@eifr @jonnylangefeld

this would be the way to handle merging:

// sign in anonymously
const { data, error } = await supabase.auth.signInAnonymously()

// try to link to a user 
const { data, error } = await supabase.auth.updateUser({ email: "[email protected]" })

// or if you want to link an oauth identity
const { data, error } = await supabase.auth.linkIdentity({ provider: 'google' })

// since the user is an existing user, an error will be thrown
// handle the error by just asking the user to sign in to the existing account
const { data: { user: newUser} , error } = await signInWithPassword(...)

// reassign any entities tied to the anonymous user previously
const { data, error } = await supabase.from("table1").update().eq("id", newUser.id)

thanks for calling this out, i realised an outline of this is missing from the docs and it would be useful to add this to it

But the issue is that at this point the sb-127-auth-token cookie for the anonymous user is gone. So I don't have the data of the anonymous user anymore at this point.

@jonnylangefeld, i think the issue here is because linkIdentity removes the session locally if an error is returned - will look into a fix for this!

kangmingtay avatar Aug 29 '24 18:08 kangmingtay

@eifr @jonnylangefeld

this would be the way to handle merging:

// sign in anonymously
const { data, error } = await supabase.auth.signInAnonymously()

// try to link to a user 
const { data, error } = await supabase.auth.updateUser({ email: "[email protected]" })

// or if you want to link an oauth identity
const { data, error } = await supabase.auth.linkIdentity({ provider: 'google' })

// since the user is an existing user, an error will be thrown
// handle the error by just asking the user to sign in to the existing account
const { data: { user: newUser} , error } = await signInWithPassword(...)

// reassign any entities tied to the anonymous user previously
const { data, error } = await supabase.from("table1").update().eq("id", newUser.id)

thanks for calling this out, i realised an outline of this is missing from the docs and it would be useful to add this to it

But the issue is that at this point the sb-127-auth-token cookie for the anonymous user is gone. So I don't have the data of the anonymous user anymore at this point.

@jonnylangefeld, i think the issue here is because linkIdentity removes the session locally if an error is returned - will look into a fix for this!

Is there an update on this? I am trying to handle linkIdentity gracefully, but I am losing the session as @jonnylangefeld pointed out. Thanks!

trevorpfiz avatar Oct 11 '24 22:10 trevorpfiz

Session is still being cleared on auth error. Is there any update?

Groompel avatar Nov 23 '24 02:11 Groompel

Session is still being cleared on auth error. Is there any update?

I don't have any updates

trevorpfiz avatar Nov 23 '24 05:11 trevorpfiz

This is a big blocker. Previously when using firebase anonymous user, this issue did not exist.

dekmaster avatar Nov 24 '24 18:11 dekmaster

Still looking for a solution here, scared to rely on anonymous users until this seems reliably solved.

Marviel avatar Nov 27 '24 18:11 Marviel

What this amounts to is that the anonymous sign-ins upgrade with linkIdentity works ONLY the first time a user EVER signs in with their oauth account. But you can't know up front whether it's going to fail, and AFAICT (with google) the ONLY way to know that it's even failed is by parsing url prams.

Personally, the documentation might as well say "anonymous sign-ins are not supported with oauth", because the caveat is so large, that might as well be the whole truth.

UPDATE:

  • I think this is a communication issue. I was able to resolve this issue by NEVER using linkIdentity. The only thing I ever want to keep from an anonymous user is the data they've created while anon. Before any sign in, simply note down the anon user id, then when signed in, have them migrate the data from that anon user to themselves as the authorised user. It feels like to me this was a badly thought-out misapplication in the documentation of a capability, but it's not the appropriate way to achieve what most people want. linkIdentity is more suitable for taking a non-anonymous account A and adding google-auth to it. If google auth is the primary user-identifying method though, then you don't ever need linked accounts.

danielrob avatar Jun 16 '25 02:06 danielrob

What this amounts to is that the anonymous sign-ins upgrade with linkIdentity works ONLY the first time a user EVER signs in with their oauth account. But you can't know up front whether it's going to fail, and AFAICT (with google) the ONLY way to know that it's even failed is by parsing url prams.

Personally, the documentation might as well say "anonymous sign-ins are not supported with oauth", because the caveat is so large, that might as well be the whole truth.

UPDATE:

  • I think this is a communication issue. I was able to resolve this issue by NEVER using linkIdentity. The only thing I ever want to keep from an anonymous user is the data they've created while anon. Before any sign in, simply note down the anon user id, then when signed in, have them migrate the data from that anon user to themselves as the authorised user. It feels like to me this was a badly thought-out misapplication in the documentation of a capability, but it's not the appropriate way to achieve what most people want. linkIdentity is more suitable for taking a non-anonymous account A and adding google-auth to it. If google auth is the primary user-identifying method though, then you don't ever need linked accounts.

Thanks for this. Was struggling with how to approach this, but this is a good way that fits my use case!

dariuscosden avatar Jun 16 '25 14:06 dariuscosden

What this amounts to is that the anonymous sign-ins upgrade with linkIdentity works ONLY the first time a user EVER signs in with their oauth account. But you can't know up front whether it's going to fail, and AFAICT (with google) the ONLY way to know that it's even failed is by parsing url prams.

Personally, the documentation might as well say "anonymous sign-ins are not supported with oauth", because the caveat is so large, that might as well be the whole truth.

UPDATE:

I think this is a communication issue. I was able to resolve this issue by NEVER using linkIdentity. The only thing I ever want to keep from an anonymous user is the data they've created while anon. Before any sign in, simply note down the anon user id, then when signed in, have them migrate the data from that anon user to themselves as the authorised user. It feels like to me this was a badly thought-out misapplication in the documentation of a capability, but it's not the appropriate way to achieve what most people want. linkIdentity is more suitable for taking a non-anonymous account A and adding google-auth to it. If google auth is the primary user-identifying method though, then you don't ever need linked accounts.

This doesn't work in my case since I'm using the anonymous user id to track analytics, so losing the user id means losing this data

Dev-Salem avatar Sep 06 '25 11:09 Dev-Salem