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

🔥 Access to FIRAuthErrorUserInfoUpdatedCredentialKey with Apple Sign In #3952

Closed
1 of 5 tasks
timothystewart6 opened this issue Jul 15, 2020 · 29 comments · Fixed by #4359 or #4487
Closed
1 of 5 tasks

🔥 Access to FIRAuthErrorUserInfoUpdatedCredentialKey with Apple Sign In #3952

timothystewart6 opened this issue Jul 15, 2020 · 29 comments · Fixed by #4359 or #4487
Labels
help: ios Needs help implementing or reviewing a PR relating to iOS code. platform: ios plugin: authentication Firebase Authentication

Comments

@timothystewart6
Copy link

timothystewart6 commented Jul 15, 2020

Issue

Hello. I am integrating Apple Sign in now. It works great except for one small piece. It's the flow from Firebase Anonymous -> Link User -> User already exists in Firebase -> Create new account.

When calling the code below and the firebase user already exists, the call fails which I think is expected because I am trying to reuse the nonce that was used in the first call to link accounts. However I thought I should get an updated credential in the error response.

  const appleCredential = auth.AppleAuthProvider.credential(idToken, nonce)
  const linkResponse = await auth().currentUser.linkWithCredential(appleCredential)
  const signInResponse = await auth().signInWithCredential(appleCredential)
  (error: NativeFirebaseError: [auth/unknown] Duplicate credential received. Please try again with a new credential.)

Error

NativeFirebaseError: [auth/unknown] Duplicate credential received. Please try again with a new credential.

I can successfully sign in with Apple if I sign in again, however the user need to authenticate to Apple again.

I found this issue and it looks the native side should return an updated credential key in this scenario that I can use on my next call without having to reauth with Apple.

error.userInfo[FIRAuthErrorUserInfoUpdatedCredentialKey] returned in the error. Currently error.userInfo does not return this key. This key would prevent another request to sign in.

firebase/firebase-ios-sdk#4434

I see you closed a similar issue but I think firebase has implemented the native side.

Thank you for your consideration.


Project Files

Javascript

Click To Expand

package.json:

(also saw the same issue on the latest version)

    "@react-native-firebase/analytics": "^6.7.2",
    "@react-native-firebase/app": "^6.7.1",
    "@react-native-firebase/auth": "^6.7.1",
    "@react-native-firebase/crashlytics": "^6.7.1",
    "@react-native-firebase/dynamic-links": "^6.7.1",```
    "react-native": "0.61.5",

#### `firebase.json` for react-native-firebase v6:

```json
# N/A

iOS

Click To Expand

ios/Podfile:

  • I'm not using Pods
  • I'm using Pods and my Podfile looks like:
# N/A

AppDelegate.m:

// N/A


Android

Click To Expand

Have you converted to AndroidX?

  • my application is an AndroidX application?
  • I am using android/gradle.settings jetifier=true for Android compatibility?
  • I am using the NPM package jetifier for react-native compatibility?

android/build.gradle:

// N/A

android/app/build.gradle:

// N/A

android/settings.gradle:

// N/A

MainApplication.java:

// N/A

AndroidManifest.xml:

<!-- N/A -->


Environment

Click To Expand

react-native info output:

System:
    OS: macOS 10.15.5
    CPU: (16) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 1.09 GB / 16.00 GB
    Shell: 5.7.1 - /bin/zsh
  Binaries:
    Node: 12.16.1 - ~/.nvm/versions/node/v12.16.1/bin/node
    Yarn: 1.22.4 - ~/.yarn/bin/yarn
    npm: 6.13.4 - ~/.nvm/versions/node/v12.16.1/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  Managers:
    CocoaPods: 1.9.3 - /Users/someone/.rvm/gems/ruby-2.6.0/bin/pod
  SDKs:
    iOS SDK:
      Platforms: iOS 13.5, DriverKit 19.0, macOS 10.15, tvOS 13.4, watchOS 6.2
    Android SDK:
      API Levels: 28
      Build Tools: 28.0.3
      System Images: android-28 | Google Play Intel x86 Atom_64
      Android NDK: Not Found
  IDEs:
    Android Studio: 4.0 AI-193.6911.18.40.6514223
    Xcode: 11.5/11E608c - /usr/bin/xcodebuild
  Languages:
    Java: 1.8.0_212 - /usr/bin/javac
    Python: 2.7.16 - /usr/bin/python
  npmPackages:
    @react-native-community/cli: Not Found
    react: 16.9.0 => 16.9.0
    react-native: 0.61.5 => 0.61.5
  npmGlobalPackages:
    *react-native*: Not Found```

<!-- change `[ ]` to `[x]` to select an option(s) -->

- **Platform that you're experiencing the issue on**:
  - [x] iOS
  - [ ] Android
  - [ ] **iOS** but have not tested behavior on Android
  - [ ] **Android** but have not tested behavior on iOS
  - [ ] Both
- **`react-native-firebase` version you're using that has this issue:**
  - `e.g. 5.4.3`
- **`Firebase` module(s) you're using that has the issue:**
  - `e.g. Instance ID`
- **Are you using `TypeScript`?**
  - `Y/N` & `VERSION`

</p>
</details>

<!-- Thanks for reading this far down ❤️  -->
<!-- High quality, detailed issues are much easier to triage for maintainers -->

<!-- For bonus points, if you put a 🔥 (:fire:) emojii at the start of the issue title we'll know -->
<!-- that you took the time to fill this out correctly, or, at least read this far -->

---

- 👉 Check out [`React Native Firebase`](https://twitter.com/rnfirebase) and [`Invertase`](https://twitter.com/invertaseio) on Twitter for updates on the library.
@mikehardy
Copy link
Collaborator

Interesting, I may have misunderstood the nature of the problem previously - frequently happens / always learning - and it looks like what's happening is that the native SDKs are providing an updated credential which you could use for a new call (as it will have an updated nonce) but it is (perhaps?) not passed through all the layers so you don't have access to it, so you can't use it from react-native-firebase?

If that understanding is correct, can you imagine a patch that would fix it? Is likely a small change to the iOS code here to pass things through

I think this is the code hit when link-with-credential blows up: https://github.com/invertase/react-native-firebase/blob/master/packages/auth/ios/RNFBAuth/RNFBAuthModule.m#L799

which delegates to

- (void)promiseRejectAuthException:(RCTPromiseRejectBlock)reject error:(NSError *)error {
NSDictionary *jsError = [self getJSError:(error)];
[RNFBSharedUtils rejectPromiseWithUserInfo:reject userInfo:(NSMutableDictionary *) @{
@"code": [jsError valueForKey:@"code"],
@"message": [jsError valueForKey:@"message"],
@"nativeErrorMessage": [jsError valueForKey:@"nativeErrorMessage"],
}];
}

which *uses this to extract parts:

- (NSDictionary *)getJSError:(NSError *)error {
NSString *code = AuthErrorCode_toJSErrorCode[error.code];
NSString *message = [error localizedDescription];
NSString *nativeErrorMessage = [error localizedDescription];
if (code == nil)
code = @"unknown";
// TODO(Salakar): replace these with a AuthErrorCode_toJSErrorMessage map (like codes now does)
switch (error.code) {
case FIRAuthErrorCodeInvalidCustomToken:
message = @"The custom token format is incorrect. Please check the documentation.";
break;
case FIRAuthErrorCodeCustomTokenMismatch:message = @"The custom token corresponds to a different audience.";
break;
case FIRAuthErrorCodeInvalidCredential:message = @"The supplied auth credential is malformed or has expired.";
break;
case FIRAuthErrorCodeInvalidEmail:message = @"The email address is badly formatted.";
break;
case FIRAuthErrorCodeWrongPassword:message = @"The password is invalid or the user does not have a password.";
break;
case FIRAuthErrorCodeUserMismatch:
message = @"The supplied credentials do not correspond to the previously signed in user.";
break;
case FIRAuthErrorCodeRequiresRecentLogin:
message =
@"This operation is sensitive and requires recent authentication. Log in again before retrying this request.";
break;
case FIRAuthErrorCodeAccountExistsWithDifferentCredential:
message =
@"An account already exists with the same email address but different sign-in credentials. Sign in using a provider associated with this email address.";
break;
case FIRAuthErrorCodeEmailAlreadyInUse:message = @"The email address is already in use by another account.";
break;
case FIRAuthErrorCodeCredentialAlreadyInUse:
message = @"This credential is already associated with a different user account.";
break;
case FIRAuthErrorCodeUserDisabled:message = @"The user account has been disabled by an administrator.";
break;
case FIRAuthErrorCodeUserTokenExpired:
message = @"The user's credential is no longer valid. The user must sign in again.";
break;
case FIRAuthErrorCodeUserNotFound:
message = @"There is no user record corresponding to this identifier. The user may have been deleted.";
break;
case FIRAuthErrorCodeInvalidUserToken:
message = @"The user's credential is no longer valid. The user must sign in again.";
break;
case FIRAuthErrorCodeWeakPassword:message = @"The given password is invalid.";
break;
case FIRAuthErrorCodeOperationNotAllowed:
message = @"This operation is not allowed. You must enable this service in the console.";
break;
case FIRAuthErrorCodeNetworkError:message = @"A network error has occurred, please try again.";
break;
case FIRAuthErrorCodeInternalError:message = @"An internal error has occurred, please try again.";
break;
default:break;
}
return @{
@"code": code,
@"message": message,
@"nativeErrorMessage": nativeErrorMessage,
};
}

which sends the extracted bits to

+ (void)rejectPromiseWithUserInfo:(RCTPromiseRejectBlock)reject userInfo:(NSMutableDictionary *)userInfo {
NSError *error = [NSError errorWithDomain:RNFBErrorDomain code:666 userInfo:userInfo];
reject(userInfo[@"code"], userInfo[@"message"], error);
}

I think it's the "extract parts" bit. If you could check the NSError* in getJSError to see if it has the credential thing (I think it's an NSString* ?) you might be able to just add 'userInfoUpdatedCredentialKey' as a thing that gets passed around

I took the time to find the things I think need to be changed but the hard part is not the change, it's the testing :-), and no one is more motivated then the reporter in my experience, so hopefully this is enough to get the basic code / string passing plumbed in then you can check it and see success?

Then I strongly recommend https://github.com/ds300/patch-package to persist the change so you stay functional, and propose a PR (or just post the patch maybe) to fix it for good?

@timothystewart6
Copy link
Author

Hey thanks! That's exactly what's going on. Let me see if I can get the error from native back to JSVille.

@timothystewart6
Copy link
Author

After a few attempts, it looks like the credential is null from this error response. I returned and logged out userInfo

{
    "FIRAuthErrorUserInfoNameKey": "ERROR_CREDENTIAL_ALREADY_IN_USE",
    "FIRAuthErrorUserInfoEmailKey": "someone@example.com",
    "FIRAuthErrorUserInfoUpdatedCredentialKey": null,
    "NSLocalizedDescription": "This credential is already associated with a different user account."
}

I will check with the latest.

@timothystewart6
Copy link
Author

Same with the latest unfortunately. I'll see if I can figure something else out as to why this is null

@mikehardy
Copy link
Collaborator

That's unexpected - perhaps you can set a breakpoint in Xcode on the actual firebase-ios-sdk return to inspect everything right there? Or have already tried that? At least this is an easy case to reproduce, but what a pain.

@andersonaddo andersonaddo added the plugin: authentication Firebase Authentication label Jul 16, 2020
@AppKidd
Copy link

AppKidd commented Jul 19, 2020

Has there been any progress on this at all? Have spent the past few hours trying to resolve this by playing with the source but I'm not experienced in anyway in bridging so haven't had any luck. We currently have to display Apple Sign In twice to upgrade anon users so would appreciate a fix.

@mikehardy
Copy link
Collaborator

I believe you @AppKidd and @timothystewart6 are the only interested parties at the moment. I'm still surprised that this isn't bridgeable based on the upstream firebase-ios-sdk issue. Your direct experience is obviously valuable as you're playing with the code but it seemed that the Obj-C should have the value coming back at some point, on their return object 🤔

@AppKidd
Copy link

AppKidd commented Jul 21, 2020

Thanks @mikehardy. I'll try and have another look later this week.

@One-Q
Copy link

One-Q commented Aug 4, 2020

Hey,
I have the exact same issue... Did someone manage to resolve this ?
Thanks

@AppKidd
Copy link

AppKidd commented Aug 7, 2020

@One-Q Hey, sadly not. I spent a bit more time on it but I'm just not knowledgable enough in Objective-C to fully trace where the issue lies. Would really appreciate it if we could get a fix for this @mikehardy.

@mikehardy
Copy link
Collaborator

I appreciate the effort truly - I think we're close here. My Obj-C isn't fantastic either, perhaps @Salakar has someone with better skills that can pick it up when they scan the queue for task distribution?

If not I can give it a try but it isn't at the top of my queue at the moment (apologies)

@AppKidd
Copy link

AppKidd commented Aug 7, 2020

There's a free coffee on me for the person that gets to the bottom of this ☕🤓

@Salakar
Copy link
Member

Salakar commented Aug 7, 2020

Hmmm if FIRAuthErrorUserInfoUpdatedCredentialKey is null on the native error side then this would be down to the Firebase iOS SDK - once it's coming through there properly then we could definitely expose this to JS land.

There's also another secondary bug here which should probably also be fixed at the same time, the error code is coming through as unknown: NativeFirebaseError: [auth/unknown] Duplicate credential received. Please try again with a new credential.

@Salakar Salakar added help: ios Needs help implementing or reviewing a PR relating to iOS code. platform: ios labels Aug 7, 2020
@mikehardy
Copy link
Collaborator

firebase/firebase-ios-sdk#4434 (comment) was reported success, which gave me hope. Perhaps false hope, undetermined without a look I think

@timothystewart6
Copy link
Author

thank you everyone!

@stale
Copy link

stale bot commented Sep 11, 2020

Hello 👋, to help manage issues we automatically close stale issues.
This issue has been automatically marked as stale because it has not had activity for quite some time. Has this issue been fixed, or does it still require the community's attention?

This issue will be closed in 15 days if no further activity occurs.
Thank you for your contributions.

@stale stale bot added the Type: Stale Issue has become stale - automatically added by Stale bot label Sep 11, 2020
@AppKidd
Copy link

AppKidd commented Sep 14, 2020

Hi @Salakar, are we any closer to seeing a fix for this in react-native-firebase? My understanding at the time was that the iOS SDK was exposing FIRAuthErrorUserInfoUpdatedCredentialKey (logging the function displayed the key), it was just that it wasn't reaching the JS side.

@stale stale bot removed the Type: Stale Issue has become stale - automatically added by Stale bot label Sep 14, 2020
@mikehardy
Copy link
Collaborator

@AppKidd my understanding from the comments above was that it should be doing that but the people that investigated did not see it on the native side, so there's nothing to bridge. We have no positive confirmation here (yet?) that anyone was able to see the desired property from the firebase-ios-sdk

@CodeToTee
Copy link

I'm seeing a similar problem. In my case, I have already authenticated my user through Google, and when I try Facebook, I'm (expectedly) getting the duplicate credential error. But, as reported above, the conflicting email and credentials aren't being sent back in the userInfo object as part of the error response. It looks like the values are actually in the error response down in promiseRejectAuthException (thanks @mikehardy for showing the code paths...a big help). I'm going to try to resolve this by passing back up the FIRAuthErrorUserInfoEmailKey and FIRAuthErrorUserInfoUpdatedCredentialKey keys, then file a PR.

@henrik-d
Copy link
Contributor

henrik-d commented Oct 2, 2020

I have the same problem. I have absolutely no knowledge of ObjectiveC, but inserted a little log into RNFBAuthModule.m

if (user) {
    [user linkAndRetrieveDataWithCredential:credential
                                 completion:^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) {
                                   if (error) {
                                     NSLog(@"FIRAuthErrorUserInfoUpdatedCredentialKey: %@", [error.userInfo[FIRAuthErrorUserInfoUpdatedCredentialKey] description]);
                                     [self promiseRejectAuthException:reject error:error];
                                   } else {
                                     [self promiseWithAuthResult:resolve rejecter:reject authResult:authResult];
                                   }
                                 }];
  }

It seems that updated credential is not null (anymore)!
The log shows:FIRAuthErrorUserInfoUpdatedCredentialKey: <FIROAuthCredential: 0x28275a210>

Would be awesome, if this could be passed to JS!

@mikehardy
Copy link
Collaborator

You join me in the "I suck at Objective-C" club then :-), no offense intended, but truthful for me at least. That was the first thing we needed I think - just confirmation it existed since a previous result was negative. If anyone posts a PR here that actually knows Objective-C 😅 I will happily merge. I think it will just be one layer up, in the promiseRejectAuthException method a value maybe just copied from one object to another? (then documented)

@CodeToTee
Copy link

My Objective-C is also pretty poor, but I know the credential could be added. I started digging into this, then realized that my JavaScript also isn't as good as it should be either! When you console.log() the error, you don't see all the fields. I figured out that some of them are actually present, you just need to know where to look for them. I was able to work around my specific issue (i.e. needing the duplicate credential email back), as I already had the email available. So, I have Google and Facebook working properly. I'm next adding Apple, so I figured I'd hit this issue head-on and have to resolve it one way or another. I'll post my results soon...thats my next task. :-)

@henrik-d
Copy link
Contributor

henrik-d commented Oct 4, 2020

Okay, I digged further into this.

I tried to add the [error userInfo][FIRAuthErrorUserInfoUpdatedCredentialKey] to the userInfo of the JS-Error. I think the problem is, that FIRAuthCredential can't be serialized properly. The only accessible property of FIRAuthCredential is provider, but we also need the token and secret for a functional AuthCredential.

I took a look how the flutter guys are solving this, and it seems that instead of passing the hole credential, they

  1. store the credential on the native side (see here)
  2. pass just an identifier to the JS-Side (in their case Dart-Side)
  3. and get the stored credential back, when passing this identifier (see here).

Any thoughts on that?

Edit: I implemented this solutions for test purposes and it seems to work!

@mikehardy
Copy link
Collaborator

mikehardy commented Oct 4, 2020

@henrik-d haha "the flutter guys" are also Invertase, who created this but since FlutterFire is newer it gets shiny new code where as react-native-firebase was coded up when this specific underlying stuff didn't exist.

Which is to say: if it works in FlutterFire as a strategy it should work perfectly here, and if you have a coded solution that is fantastic! Post up a PR and I'll get my button-clicking finger ready to hit the merge button :-)

@henrik-d
Copy link
Contributor

henrik-d commented Oct 5, 2020

@mikehardy Hehe, okay I didn't know that ;)

I'm now thinking about the android side. We don't use Apple-Auth on Android in our project, but it should be the same issue there, shouldn't it?

I also took a look into the tests, but it seems that social providers aren't tested (I assume because it would require heavy mocking)?

@mikehardy
Copy link
Collaborator

Unsure on testing @henrik-d but I understand if there is no scaffolding built up for social auth testing that the burden of adding testing is quite high, I can accept a PR without coverage if you attest you've tested it (note the PR has an action that generates patches now, so for every PR you get a downloadable zip of patch-package format patches for testing - makes it easy!). Unsure on the Android side, I would assume it's the same, but similar to the testing, I won't make perfect the enemy of good. The react-native-apple-authentication library (which also Invertase created and maintained by me 😄 ) only just added Android support a week or two ago which shows you how important it is versus Apple (almost unimportant).

The summary being: if you just PR an ios-only fix without test cover but that you have tested personally, I'll happily merge it :-)

@mikehardy
Copy link
Collaborator

This is fixed on iOS now thanks to @henrik-d 🏆 - if anyone wants to take it on for Android please feel free. I merged the single-platform fix because I think Apple Sign On is much much much more prevalent as iOS only so it had standalone value

@AppKidd
Copy link

AppKidd commented Oct 7, 2020

Thanks @henrik-d @mikehardy - glad to see this is fixed. 👍

@henrik-d
Copy link
Contributor

henrik-d commented Oct 8, 2020

@mikehardy Thanks for your kind support. For sake of completeness, here is how I use it now:

try {
    await currentUser.linkWithCredential(credential);
} catch (error) {
    if (error.code === 'auth/credential-already-in-use' && error.userInfo.authCredential) {
        await auth().signInWithCredential(error.userInfo.authCredential);
    } else {
        throw error;
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help: ios Needs help implementing or reviewing a PR relating to iOS code. platform: ios plugin: authentication Firebase Authentication
Projects
None yet
8 participants