Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sign in with Apple and anonymous users #4434

Closed
Sephiroth87 opened this issue Dec 4, 2019 · 28 comments
Closed

Sign in with Apple and anonymous users #4434

Sephiroth87 opened this issue Dec 4, 2019 · 28 comments
Assignees

Comments

@Sephiroth87
Copy link

[REQUIRED] Step 2: Describe your environment

  • Xcode version: 11.2.1
  • Firebase SDK version: 6.13.0
  • Firebase Component: Auth
  • Component version: 6.4.0

[REQUIRED] Step 3: Describe the problem

Steps to reproduce:

In our current setup, users are always logged in anonymously, then they can deicide to login/sign in, at which point we either link the anonymous user to the used provider or sign in.

Auth.auth().currentUser?.link(with: credential) { result, error in
    if let error = error, (error as NSError).code == AuthErrorCode.credentialAlreadyInUse.rawValue {
        Auth.auth().signIn(with: credential) { result, error in
            // continue
        }
    } else {
        // continue
    }
}

This works fine with other providers (eg: Facebook), but using "Sign in with Apple", an existing user can't sign in, and I get the error:
Error Domain=FIRAuthErrorDomain Code=17094 "Duplicate credential received. Please try again with a new credential." UserInfo={NSLocalizedDescription=Duplicate credential received. Please try again with a new credential., FIRAuthErrorUserInfoNameKey=ERROR_MISSING_OR_INVALID_NONCE}

It seems that the credential is only valid for a single request, so I can't seem to be able to achieve this without having to show 2 login prompts, or discarding the anonymous user and simply using signIn

@rizafran
Copy link
Contributor

rizafran commented Dec 4, 2019

Hi @Sephiroth87, thanks for reporting this. I'll try to reproduce this behavior on my end, and I'll let you know with my findings.

@rizafran
Copy link
Contributor

rizafran commented Dec 4, 2019

@Sephiroth87 I got the same behavior on my end. As what you have mentioned, it looks like the credential is only valid for a single request in Apple provider. Upon checking this link, the "ERROR_MISSING_OR_INVALID_NONCE" means that "The request contains malformed or mismatched credentials.".

It is also mentioned here that:

For every sign-in request, generate a random string—a "nonce"—which you will use to make sure the ID token you get was granted specifically in response to your app's authentication request.

With this, it seems like you need to re-generate a random nonce string as this step is important to prevent replay attacks.

@Sephiroth87
Copy link
Author

Yes, the problem is, since the nonce is linked to the ID token, if I need to generate a new pair, I'd have to show the ASAuthorizationController again, which is not a great experience.

@rizafran
Copy link
Contributor

rizafran commented Dec 5, 2019

@Sephiroth87 Yes, you'd need to show the ASAuthorizationController again to generate a new nonce string. I even tried this using phone number, and I got an error "The SMS code has expired. Please re-send the verification code to try again". It seems that the Apple and phone number provider has the same flow and their credentials are only valid for a single request.

@Sephiroth87
Copy link
Author

So basically "Sign in with Apple" and anonymous users are just incompatible?

@morganchen12
Copy link
Contributor

@renkelvin can you answer this last question? This sounds like it would be an issue for all nonce-related flows in the future, not just Apple.

@renkelvin
Copy link
Contributor

Try linking then sign in are actually 2 separate requests, so to be OIDC compliant, the nonce has to be different. And yeah, it's not just for Apple, it would be the same for other OAuth requests with nonce.
@Sephiroth87 A solution to your case is to not setting nonce (it's optional). This way you can do it as the same as Facebook IDP.

@morganchen12
Copy link
Contributor

Sounds like this is working as intended, though it's potentially confusing.

@Sephiroth87
Copy link
Author

@renkelvin I tried not setting the nonce, but I get the same error...

@renkelvin
Copy link
Contributor

Yeah, it seems Apple's credential is forbidden to reuse. Let me double check with my teammates.

@renkelvin
Copy link
Contributor

renkelvin commented Dec 11, 2019

It's confirmed that you can't reuse the credential of Apple.

The solution is that, once you receive the error after linking, you can retrieve an updated credential from the error object with:
error.userInfo[FIRAuthErrorUserInfoUpdatedCredentialKey]
Then you can use the updated credential to sign in the user without the second login prompt.

@morganchen12
Copy link
Contributor

This is convoluted enough that we should probably document it.

@Sephiroth87
Copy link
Author

Thank you, it's working now 🎉
I agree with maybe adding some documentation, also as a feature request I would suggest a single function that does either the linking or the signin (unless I'm the only one using it that way)

@morganchen12
Copy link
Contributor

Docs updated at cr/288738567 (Googler-only link). They'll likely be merged and published at the end of this week or beginning of next.

The automatic linking behavior is likely something we'll keep in FirebaseUI.

@morganchen12
Copy link
Contributor

The change has been submitted and should be reflected in the docs shortly.

@FrankB1708
Copy link

I am having the exact same problem, but error.userInfo only contains the error code and localized string, no value FIRAuthErrorUserInfoUpdatedCredentialKey, so I cannot use the trick mentioned by @renkelvin
I have just done a pod update to make sure I am using the latest version of the API, but no luck.
How can you integrate sign in with Apple without having to show the ASAuthorizationController again?

@morganchen12
Copy link
Contributor

Can you share the error? If it's the same error, it's likely some weird provider behavior that requires a reauth under certain conditions.

@FrankB1708
Copy link

Error is ["FIRAuthErrorUserInfoNameKey": ERROR_MISSING_OR_INVALID_NONCE, "NSLocalizedDescription": Duplicate credential received. Please try again with a new credential.]
but userInfo does not contain any updated credential like it is described in this thread

@morganchen12
Copy link
Contributor

Does this always happen, even with a very recent sign-in?

@FrankB1708
Copy link

Yes it does happen next time I try to sign in.
Scenario is:

  1. successful login with Apple for first time
  2. save identityToken and nonce in UserDefaults (I do not want to generate a nonce each time to avoid having to display ASAuthorizationController at each app launch)
  3. Stop the app
  4. Restart the app, at which point it creates an Oath credential with saved identityToken and nonce and tries to sign in to Firebase
  5. login callback returns the error mentioned without any updated credential

@morganchen12
Copy link
Contributor

I am not a security engineer, but you should definitely get some expert advice before storing auth stuff in user defaults.

In this case you're receiving the error because you're trying to re-use a nonce, and they exist specifically to avoid being re-used.

@FrankB1708
Copy link

Thanks @morganchen12
Now that you say it, this is actually obvious.
That being said, I could not find any other way to authenticate user to firebase with Apple without displaying again the ASAuthorizationController.
I understand this now becomes out of topic for this thread, but then how do I manage to have my user signed in with apple at every app launch without displaying ASAuthorizationController?
I need this to have the same uid across multiple devices of the user. This is so I can sync their data stored in Firebase Realtime database without relaxing security rules ($uid === auth.uid) on user node.

@morganchen12
Copy link
Contributor

Firebase Auth will automatically persist your user in Keychain, so for normal user tasks you won't need to reauthenticate. For account-related tasks like linking a provider you will still need to reauthenticate.

You should be able to fetch the uid without reauthenticating.

@FrankB1708
Copy link

OK, I get it now and it does work.
Thanks a lot and sorry for my confusion

@morganchen12
Copy link
Contributor

No problem!

@simontgs
Copy link

simontgs commented Feb 21, 2020

Hi, we're having the issue with Unity and the Apple Sign In Popup coming up every time.
We're getting the Duplicate credentials error as we're trying to avoid the Apple Sign In pop up when we reconnect.
We store the idToken off on the first login and then use that on subsequent logins ..but this throws the exception - just a few questions on the above thread, how do you 'fetch the uid without reauthenticating' - if the user has switched their apple id etc, is this valid?
We tried an empty string soln etc for the nonce and no luck.

We are on firebase 6.11 for Unity

thnx

@morganchen12
Copy link
Contributor

Hey @simontgs, this thread has gotten a little out of hand. For Unity-related issues, please file a support ticket to Firebase Support.

The user persistence is something that Auth does by default on iOS via Keychain, so it should work without requiring you to store any login information. Are you able to reproduce this bug in an iOS-only project?

@simontgs
Copy link

Hey morgan, thanks, we haven't tried a pure ios project only, we're just using Unity.

We sign in with Apple Sign In ( Unity plugin ) - received the idToken from Apple and store that off

We then use this in the following code. Note: first time in is fine, second time in, we pull the idToken out of our PlayerPrefs and feed it in and get the duplicate credential as a result.
We have tried several alternatives to no avail.

  1.   Call Unity Sign In with Apple, retrieve the id token and store it
    

Validate the token with firebase and sign in...

 2)   var credential = OAuthProvider.GetCredential( "apple.com",token,  null, Guid.NewGuid().ToString());
  3)   var fbUser = await auth.SignInWithCredentialAsync(credential);

I'll fire an email to firebase support, thnx

@firebase firebase locked and limited conversation to collaborators Feb 23, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

8 participants
@renkelvin @Sephiroth87 @morganchen12 @FrankB1708 @simontgs @google-oss-bot @rizafran and others