Repository: firebase/firebase-ios-sdk (language: cpp)

Issue:
Anonymous Auth Verify Can't Connect Account for Email / Password
### Description

# Expected
Using Firebase anonymous authentication, and allowing a user to sign up with email / password to create their "permanent account"

# Actual
Using Firebase anonymous authentication I am able to have an anonymous user, but then using Email / Password sign up and connecting the anonymous user to this info, it fails with the error "Please verify the new email before changing email"

### Reproducing the issue

I was mostly following this example Firebase code, the `auth-account-linking` example, but when trying to sign in I would get the error "Please verify the new email before changing email" Sorry I don't have a full test project at the moment.

I was only doing anonymous auth and Email / password, not using Sign in With Apple or Google


[Favourites.zip](https://github.com/firebase/firebase-ios-sdk/files/12838400/Favourites.zip)


### Firebase SDK Version

10.15

### Xcode Version

15.0

### Installation Method

Swift Package Manager

### Firebase Product(s)

Authentication

### Targeted Platforms

iOS

### Relevant Log Output

_No response_

### If using Swift Package Manager, the project's Package.resolved

<!--- Look below for instructions on how to share your Package.resolved. --->

<details>
<summary>Expand <code>Package.resolved</code> snippet</summary>
<br>

```json

Replace this line with the contents of your Package.resolved.

```

</details>


### If using CocoaPods, the project's Podfile.lock

<!--- Look below for instructions on how to share your Podfile.lock. --->

<details>
<summary>Expand <code>Podfile.lock</code> snippet</summary>
<br>

```yml

Replace this line with the contents of your Podfile.lock!

```

</details>



Discussion / hints:
I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.
See #11886 for a duplicate, explanation and workaround.

I'll leave this issue open to track updating the samples.

Fix patch:
diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md
index 9c1c55cfbf4..3c7447e8e47 100644
--- a/FirebaseAuth/CHANGELOG.md
+++ b/FirebaseAuth/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 10.17.0
+- [fixed] Fix a bug where anonymous account can't be linked with email password credential. (#11911)
+
 # 10.16.0
 - [added] Added custom auth domain support in recaptcha v2 authentication flows. (#7553)
 
diff --git a/FirebaseAuth/Sources/Auth/FIRAuth.m b/FirebaseAuth/Sources/Auth/FIRAuth.m
index 092d357b04e..d2e7c615c95 100644
--- a/FirebaseAuth/Sources/Auth/FIRAuth.m
+++ b/FirebaseAuth/Sources/Auth/FIRAuth.m
@@ -37,7 +37,6 @@
 #import "FirebaseAuth/Sources/AuthProvider/FIRAuthCredential_Internal.h"
 #import "FirebaseAuth/Sources/AuthProvider/GameCenter/FIRGameCenterAuthCredential.h"
 #import "FirebaseAuth/Sources/AuthProvider/OAuth/FIROAuthCredential_Internal.h"
-#import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h"
 #import "FirebaseAuth/Sources/Backend/FIRAuthRequestConfiguration.h"
 #import "FirebaseAuth/Sources/Backend/RPC/FIRCreateAuthURIRequest.h"
 #import "FirebaseAuth/Sources/Backend/RPC/FIRCreateAuthURIResponse.h"
@@ -165,11 +164,6 @@
  */
 static NSString *const kRevertSecondFactorAdditionRequestType = @"REVERT_SECOND_FACTOR_ADDITION";
 
-/** @var kMissingRecaptchaTokenErrorPrefix
-    @brief The prefix of the error message of missing recaptcha token during authenticating.
- */
-static NSString *const kMissingRecaptchaTokenErrorPrefix = @"MISSING_RECAPTCHA_TOKEN";
-
 /** @var kMissingPasswordReason
     @brief The reason why the @c FIRAuthErrorCodeWeakPassword error is thrown.
     @remarks This error message will be localized in the future.
@@ -1994,6 +1988,7 @@ - (void)internalCreateUserWithEmail:(NSString *)email
       [[FIRSignUpNewUserRequest alloc] initWithEmail:email
                                             password:password
                                          displayName:nil
+                                             idToken:nil
                                 requestConfiguration:_requestConfiguration];
   if (![request.password length]) {
     completion(
diff --git a/FirebaseAuth/Sources/Auth/FIRAuth_Internal.h b/FirebaseAuth/Sources/Auth/FIRAuth_Internal.h
index cdb1e33d9f6..d259458f4f7 100644
--- a/FirebaseAuth/Sources/Auth/FIRAuth_Internal.h
+++ b/FirebaseAuth/Sources/Auth/FIRAuth_Internal.h
@@ -16,6 +16,7 @@
 
 #import <Foundation/Foundation.h>
 #import "FirebaseAuth/Interop/FIRAuthInterop.h"
+#import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h"
 #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuth.h"
 #import "FirebaseCore/Extension/FIRLogger.h"
 
diff --git a/FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserRequest.h b/FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserRequest.h
index e8b3196fe34..f8baf5df4ba 100644
--- a/FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserRequest.h
+++ b/FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserRequest.h
@@ -38,6 +38,11 @@ NS_ASSUME_NONNULL_BEGIN
  */
 @property(nonatomic, copy, nullable) NSString *displayName;
 
+/** @property idToken
+    @brief The idToken of the user.
+ */
+@property(nonatomic, copy, nullable) NSString *idToken;
+
 /** @property captchaResponse
     @brief Response to the captcha.
  */
@@ -74,6 +79,7 @@ NS_ASSUME_NONNULL_BEGIN
 - (nullable instancetype)initWithEmail:(nullable NSString *)email
                               password:(nullable NSString *)password
                            displayName:(nullable NSString *)displayName
+                               idToken:(nullable NSString *)idToken
                   requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration
     NS_DESIGNATED_INITIALIZER;
 
diff --git a/FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserRequest.m b/FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserRequest.m
index e61bcfc0597..2a31249601c 100644
--- a/FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserRequest.m
+++ b/FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserRequest.m
@@ -38,6 +38,11 @@
  */
 static NSString *const kDisplayNameKey = @"displayName";
 
+/** @var kIDToken
+    @brief The key for the "kIDToken" value in the request.
+ */
+static NSString *const kIDToken = @"idToken";
+
 /** @var kCaptchaResponseKey
     @brief The key for the "captchaResponse" value in the request.
  */
@@ -68,12 +73,14 @@ @implementation FIRSignUpNewUserRequest
 - (nullable instancetype)initWithEmail:(nullable NSString *)email
                               password:(nullable NSString *)password
                            displayName:(nullable NSString *)displayName
+                               idToken:(nullable NSString *)idToken
                   requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration {
   self = [super initWithEndpoint:kSignupNewUserEndpoint requestConfiguration:requestConfiguration];
   if (self) {
     _email = [email copy];
     _password = [password copy];
     _displayName = [displayName copy];
+    _idToken = [idToken copy];
     _returnSecureToken = YES;
   }
   return self;
@@ -84,6 +91,7 @@ - (nullable instancetype)initWithRequestConfiguration:
   self = [self initWithEmail:nil
                     password:nil
                  displayName:nil
+                     idToken:nil
         requestConfiguration:requestConfiguration];
   return self;
 }
@@ -99,6 +107,9 @@ - (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)
   if (_displayName) {
     postBody[kDisplayNameKey] = _displayName;
   }
+  if (_idToken) {
+    postBody[kIDToken] = _idToken;
+  }
   if (_captchaResponse) {
     postBody[kCaptchaResponseKey] = _captchaResponse;
   }
diff --git a/FirebaseAuth/Sources/User/FIRUser.m b/FirebaseAuth/Sources/User/FIRUser.m
index e40dade55af..c3a161eff75 100644
--- a/FirebaseAuth/Sources/User/FIRUser.m
+++ b/FirebaseAuth/Sources/User/FIRUser.m
@@ -43,6 +43,8 @@
 #import "FirebaseAuth/Sources/Backend/RPC/FIRSetAccountInfoResponse.h"
 #import "FirebaseAuth/Sources/Backend/RPC/FIRSignInWithGameCenterRequest.h"
 #import "FirebaseAuth/Sources/Backend/RPC/FIRSignInWithGameCenterResponse.h"
+#import "FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserRequest.h"
+#import "FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserResponse.h"
 #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionRequest.h"
 #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionResponse.h"
 #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyCustomTokenRequest.h"
@@ -61,9 +63,9 @@
 #import "FirebaseAuth/Sources/Utilities/FIRAuthWebUtils.h"
 
 #if TARGET_OS_IOS
-#import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRPhoneAuthProvider.h"
-
 #import "FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h"
+#import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRPhoneAuthProvider.h"
+#import "FirebaseAuth/Sources/Utilities/FIRAuthRecaptchaVerifier.h"
 #endif
 
 NS_ASSUME_NONNULL_BEGIN
@@ -582,7 +584,6 @@ - (void)setTokenService:(FIRSecureTokenService *)tokenService
 }
 
 #pragma mark -
-
 /** @fn updateEmail:password:callback:
     @brief Updates email address and/or password for the current user.
     @remarks May fail if there is already an email/password-based account for the same email
@@ -1079,6 +1080,109 @@ - (void)internalVerifyBeforeUpdateEmailWithNewEmail:(NSString *)newEmail
   });
 }
 
+- (void)linkWithEmailPassword:(FIREmailPasswordAuthCredential *)credential
+                   authResult:(FIRAuthDataResult *)authResult
+                   completion:(nullable FIRAuthDataResultCallback)completion {
+  [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
+    FIRAuthRequestConfiguration *requestConfiguration = self.auth.requestConfiguration;
+    FIRSignUpNewUserRequest *request =
+        [[FIRSignUpNewUserRequest alloc] initWithEmail:credential.email
+                                              password:credential.password
+                                           displayName:nil
+                                               idToken:accessToken
+                                  requestConfiguration:requestConfiguration];
+    FIRSignupNewUserCallback signUpNewUserCallback = ^(FIRSignUpNewUserResponse *_Nullable response,
+                                                       NSError *_Nullable error) {
+      if (error) {
+        [self signOutIfTokenIsInvalidWithError:error];
+        callInMainThreadWithAuthDataResultAndError(completion, nil, error);
+      } else {
+        // Update the new token and refresh user info again.
+        self->_tokenService = [[FIRSecureTokenService alloc]
+            initWithRequestConfiguration:requestConfiguration
+                             accessToken:response.IDToken
+               accessTokenExpirationDate:response.approximateExpirationDate
+                            refreshToken:response.refreshToken];
+
+        [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
+                                             NSError *_Nullable error) {
+          if (error) {
+            callInMainThreadWithAuthDataResultAndError(completion, nil, error);
+            return;
+          }
+          FIRGetAccountInfoRequest *getAccountInfoRequest =
+              [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken
+                                               requestConfiguration:requestConfiguration];
+          [FIRAuthBackend
+              getAccountInfo:getAccountInfoRequest
+                    callback:^(FIRGetAccountInfoResponse *_Nullable response,
+                               NSError *_Nullable error) {
+                      if (error) {
+                        [self signOutIfTokenIsInvalidWithError:error];
+                        callInMainThreadWithAuthDataResultAndError(completion, nil, error);
+                        return;
+                      }
+                      self.anonymous = NO;
+                      [self updateWithGetAccountInfoResponse:response];
+                      NSError *keychainError;
+                      if (![self updateKeychain:&keychainError]) {
+                        callInMainThreadWithAuthDataResultAndError(completion, nil, keychainError);
+                        return;
+                      }
+                      [self signOutIfTokenIsInvalidWithError:error];
+                      callInMainThreadWithAuthDataResultAndError(completion, authResult, nil);
+                    }];
+        }];
+      }
+    };
+
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION)
+    if ([[FIRAuthRecaptchaVerifier sharedRecaptchaVerifier:self.auth]
+            enablementStatusForProvider:FIRAuthRecaptchaProviderPassword]) {
+      [[FIRAuthRecaptchaVerifier sharedRecaptchaVerifier:self.auth]
+          injectRecaptchaFields:request
+                       provider:FIRAuthRecaptchaProviderPassword
+                         action:FIRAuthRecaptchaActionSignUpPassword
+                     completion:^(
+                         FIRIdentityToolkitRequest<FIRAuthRPCRequest> *requestWithRecaptchaToken) {
+                       [FIRAuthBackend
+                           signUpNewUser:(FIRSignUpNewUserRequest *)requestWithRecaptchaToken
+                                callback:signUpNewUserCallback];
+                     }];
+    } else {
+      [FIRAuthBackend
+          signUpNewUser:request
+               callback:^(FIRSignUpNewUserResponse *_Nullable response, NSError *_Nullable error) {
+                 if (!error) {
+                   signUpNewUserCallback(response, nil);
+                   return;
+                 }
+                 NSError *underlyingError = [error.userInfo objectForKey:NSUnderlyingErrorKey];
+                 if (error.code == FIRAuthErrorCodeInternalError &&
+                     [[underlyingError.userInfo
+                         objectForKey:FIRAuthErrorUserInfoDeserializedResponseKey][@"message"]
+                         hasPrefix:kMissingRecaptchaTokenErrorPrefix]) {
+                   [[FIRAuthRecaptchaVerifier sharedRecaptchaVerifier:self.auth]
+                       injectRecaptchaFields:request
+                                    provider:FIRAuthRecaptchaProviderPassword
+                                      action:FIRAuthRecaptchaActionSignUpPassword
+                                  completion:^(FIRIdentityToolkitRequest<FIRAuthRPCRequest>
+                                                   *requestWithRecaptchaToken) {
+                                    [FIRAuthBackend signUpNewUser:(FIRSignUpNewUserRequest *)
+                                                                      requestWithRecaptchaToken
+                                                         callback:signUpNewUserCallback];
+                                  }];
+                 } else {
+                   signUpNewUserCallback(nil, error);
+                 }
+               }];
+    }
+#else
+[FIRAuthBackend signUpNewUser:request callback:signUpNewUserCallback];
+#endif
+  }];
+}
+
 - (void)linkWithCredential:(FIRAuthCredential *)credential
                 completion:(nullable FIRAuthDataResultCallback)completion {
   dispatch_async(FIRAuthGlobalWorkQueue(), ^{
@@ -1098,15 +1202,9 @@ - (void)linkWithCredential:(FIRAuthCredential *)credential
       FIREmailPasswordAuthCredential *emailPasswordCredential =
           (FIREmailPasswordAuthCredential *)credential;
       if (emailPasswordCredential.password) {
-        [self updateEmail:emailPasswordCredential.email
-                 password:emailPasswordCredential.password
-                 callback:^(NSError *error) {
-                   if (error) {
-                     callInMainThreadWithAuthDataResultAndError(completion, nil, error);
-                   } else {
-                     callInMainThreadWithAuthDataResultAndError(completion, result, nil);
-                   }
-                 }];
+        [self linkWithEmailPassword:emailPasswordCredential
+                         authResult:result
+                         completion:completion];
       } else {
         [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
                                              NSError *_Nullable error) {
diff --git a/FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h b/FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h
index a066fc9e1e6..0e5e4b4c072 100644
--- a/FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h
+++ b/FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h
@@ -23,6 +23,11 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
+/** @var kMissingRecaptchaTokenErrorPrefix
+    @brief The prefix of the error message of missing recaptcha token during authenticating.
+ */
+static NSString *const kMissingRecaptchaTokenErrorPrefix = @"MISSING_RECAPTCHA_TOKEN";
+
 /** @class FIRAuthErrorUtils
     @brief Utility class used to construct @c NSError instances.
  */
diff --git a/FirebaseAuth/Tests/Unit/FIRAuthTests.m b/FirebaseAuth/Tests/Unit/FIRAuthTests.m
index edb777d061a..727465551f9 100644
--- a/FirebaseAuth/Tests/Unit/FIRAuthTests.m
+++ b/FirebaseAuth/Tests/Unit/FIRAuthTests.m
@@ -138,6 +138,11 @@
  */
 static NSString *const kDisplayName = @"User Doe";
 
+/** @var kIDToken
+    @brief The fake id token.
+ */
+static NSString *const kIDToken = @"IDToken";
+
 /** @var kFakeGivenName
     @brief The fake user given name.
  */
@@ -1818,6 +1823,7 @@ - (void)testCreateUserWithEmailPasswordWithRecaptchaVerificationSuccess {
       [[FIRSignUpNewUserRequest alloc] initWithEmail:kEmail
                                             password:kFakePassword
                                          displayName:kDisplayName
+                                             idToken:kIDToken
                                 requestConfiguration:[FIRAuth auth].requestConfiguration];
   [constRequestWithRecaptchaToken injectRecaptchaFields:kFakeRecaptchaResponse
                                        recaptchaVersion:kFakeRecaptchaVersion];
@@ -1867,6 +1873,7 @@ - (void)testCreateUserWithEmailPasswordWithRecaptchaFallbackSuccess {
       [[FIRSignUpNewUserRequest alloc] initWithEmail:kEmail
                                             password:kFakePassword
                                          displayName:kDisplayName
+                                             idToken:kIDToken
                                 requestConfiguration:[FIRAuth auth].requestConfiguration];
   [constRequestWithRecaptchaToken injectRecaptchaFields:kFakeRecaptchaResponse
                                        recaptchaVersion:kFakeRecaptchaVersion];
diff --git a/FirebaseAuth/Tests/Unit/FIRSignUpNewUserRequestTests.m b/FirebaseAuth/Tests/Unit/FIRSignUpNewUserRequestTests.m
index 5016e921068..627d2c769ea 100644
--- a/FirebaseAuth/Tests/Unit/FIRSignUpNewUserRequestTests.m
+++ b/FirebaseAuth/Tests/Unit/FIRSignUpNewUserRequestTests.m
@@ -60,6 +60,16 @@
  */
 static NSString *const kTestDisplayName = @"DisplayName";
 
+/** @var kIDTokenKey
+    @brief the name of the "kIDTokenKey" property in the request.
+ */
+static NSString *const kIDTokenKey = @"idToken";
+
+/** @var kTestIDToken
+    @brief Testing id token.
+ */
+static NSString *const kTestIDToken = @"testIDToken";
+
 /** @var kPasswordKey
     @brief the name of the "password" property in the request.
  */
@@ -167,6 +177,7 @@ - (void)testSignUpNewUserRequestNotAnonymous {
       [[FIRSignUpNewUserRequest alloc] initWithEmail:kTestEmail
                                             password:kTestPassword
                                          displayName:kTestDisplayName
+                                             idToken:kTestIDToken
                                 requestConfiguration:_requestConfiguration];
   [FIRAuthBackend
       signUpNewUser:request
@@ -190,6 +201,7 @@ - (void)testSignUpNewUserRequestOptionalFields {
       [[FIRSignUpNewUserRequest alloc] initWithEmail:kTestEmail
                                             password:kTestPassword
                                          displayName:kTestDisplayName
+                                             idToken:kTestIDToken
                                 requestConfiguration:_requestConfiguration];
   request.captchaResponse = kTestCaptchaResponse;
   request.clientType = kTestClientType;
diff --git a/FirebaseAuth/Tests/Unit/FIRSignUpNewUserResponseTests.m b/FirebaseAuth/Tests/Unit/FIRSignUpNewUserResponseTests.m
index 16e747b2443..295a264fc56 100644
--- a/FirebaseAuth/Tests/Unit/FIRSignUpNewUserResponseTests.m
+++ b/FirebaseAuth/Tests/Unit/FIRSignUpNewUserResponseTests.m
@@ -159,6 +159,7 @@ - (void)testSuccessfulSignUp {
       [[FIRSignUpNewUserRequest alloc] initWithEmail:kTestEmail
                                             password:kTestPassword
                                          displayName:kTestDisplayName
+                                             idToken:kTestIDToken
                                 requestConfiguration:_requestConfiguration];
 
   __block BOOL callbackInvoked;
diff --git a/FirebaseAuth/Tests/Unit/FIRUserTests.m b/FirebaseAuth/Tests/Unit/FIRUserTests.m
index dee408722d4..3e8d92b7243 100644
--- a/FirebaseAuth/Tests/Unit/FIRUserTests.m
+++ b/FirebaseAuth/Tests/Unit/FIRUserTests.m
@@ -39,6 +39,7 @@
 #import "FirebaseAuth/Sources/Backend/RPC/FIRSecureTokenResponse.h"
 #import "FirebaseAuth/Sources/Backend/RPC/FIRSetAccountInfoRequest.h"
 #import "FirebaseAuth/Sources/Backend/RPC/FIRSetAccountInfoResponse.h"
+#import "FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserRequest.h"
 #import "FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserResponse.h"
 #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionRequest.h"
 #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionResponse.h"
@@ -2251,7 +2252,6 @@ - (void)testlinkEmailAndRetrieveDataSuccess {
                              profile:[[self class] googleProfile]
                      providerIDToken:kFacebookIDToken
                  providerAccessToken:kFacebookAccessToken];
-
   XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
   [[FIRAuth auth] signOut:NULL];
   FIRAuthCredential *facebookCredential =
@@ -2268,31 +2268,38 @@ - (void)testlinkEmailAndRetrieveDataSuccess {
                                         FIRFacebookAuthProviderID);
                   XCTAssertNil(error);
 
-                  id mockGetAccountInfoResponseUser =
-                      OCMClassMock([FIRGetAccountInfoResponseUser class]);
-                  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
-                  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
-                  OCMStub([mockGetAccountInfoResponseUser displayName])
-                      .andReturn(kEmailDisplayName);
-                  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
-                  // Get account info is expected to be invoked twice.
-                  [self
-                      expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
-                  [self
-                      expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
-
-                  OCMExpect([self->_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
-                      .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
-                                       FIRSetAccountInfoResponseCallback callback) {
-                        XCTAssertEqualObjects(request.APIKey, kAPIKey);
-                        XCTAssertEqualObjects(request.accessToken, kAccessToken);
+                  OCMExpect([self->_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]])
+                      .andCallBlock2(^(FIRSignUpNewUserRequest *_Nullable request,
+                                       FIRSignupNewUserCallback callback) {
+                        XCTAssertEqualObjects(request.email, kEmail);
                         XCTAssertEqualObjects(request.password, kFakePassword);
-                        XCTAssertNil(request.localID);
                         XCTAssertNil(request.displayName);
                         dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
-                          id mockSetAccountInfoResponse =
-                              OCMClassMock([FIRSetAccountInfoResponse class]);
-                          callback(mockSetAccountInfoResponse, nil);
+                          id mockSignUpNewUserResponse =
+                              OCMClassMock([FIRSignUpNewUserResponse class]);
+                          [self stubTokensWithMockResponse:mockSignUpNewUserResponse];
+                          callback(mockSignUpNewUserResponse, nil);
+                        });
+                      });
+
+                  OCMExpect([self->_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+                      .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
+                                       FIRGetAccountInfoResponseCallback callback) {
+                        dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+                          id mockGetAccountInfoResponseUser =
+                              OCMClassMock([FIRGetAccountInfoResponseUser class]);
+                          OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+                          OCMStub([mockGetAccountInfoResponseUser displayName])
+                              .andReturn(kEmailDisplayName);
+                          OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+                          OCMStub([mockGetAccountInfoResponseUser passwordHash])
+                              .andReturn(kPasswordHash);
+                          id mockGetAccountInfoResponse =
+                              OCMClassMock([FIRGetAccountInfoResponse class]);
+                          OCMStub([mockGetAccountInfoResponse users]).andReturn(@[
+                            mockGetAccountInfoResponseUser
+                          ]);
+                          callback(mockGetAccountInfoResponse, nil);
                         });
                       });
 
@@ -2343,31 +2350,38 @@ - (void)testlinkEmailProviderAlreadyLinkedError {
                                         FIRFacebookAuthProviderID);
                   XCTAssertNil(error);
 
-                  id mockGetAccountInfoResponseUser =
-                      OCMClassMock([FIRGetAccountInfoResponseUser class]);
-                  OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
-                  OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
-                  OCMStub([mockGetAccountInfoResponseUser displayName])
-                      .andReturn(kEmailDisplayName);
-                  OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
-                  // Get account info is expected to be invoked twice.
-                  [self
-                      expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
-                  [self
-                      expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
-
-                  OCMExpect([self->_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
-                      .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
-                                       FIRSetAccountInfoResponseCallback callback) {
-                        XCTAssertEqualObjects(request.APIKey, kAPIKey);
-                        XCTAssertEqualObjects(request.accessToken, kAccessToken);
+                  OCMExpect([self->_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]])
+                      .andCallBlock2(^(FIRSignUpNewUserRequest *_Nullable request,
+                                       FIRSignupNewUserCallback callback) {
+                        XCTAssertEqualObjects(request.email, kEmail);
                         XCTAssertEqualObjects(request.password, kFakePassword);
-                        XCTAssertNil(request.localID);
                         XCTAssertNil(request.displayName);
                         dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
-                          id mockSetAccountInfoResponse =
-                              OCMClassMock([FIRSetAccountInfoResponse class]);
-                          callback(mockSetAccountInfoResponse, nil);
+                          id mockSignUpNewUserResponse =
+                              OCMClassMock([FIRSignUpNewUserResponse class]);
+                          [self stubTokensWithMockResponse:mockSignUpNewUserResponse];
+                          callback(mockSignUpNewUserResponse, nil);
+                        });
+                      });
+
+                  OCMExpect([self->_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+                      .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
+                                       FIRGetAccountInfoResponseCallback callback) {
+                        dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+                          id mockGetAccountInfoResponseUser =
+                              OCMClassMock([FIRGetAccountInfoResponseUser class]);
+                          OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+                          OCMStub([mockGetAccountInfoResponseUser displayName])
+                              .andReturn(kEmailDisplayName);
+                          OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+                          OCMStub([mockGetAccountInfoResponseUser passwordHash])
+                              .andReturn(kPasswordHash);
+                          id mockGetAccountInfoResponse =
+                              OCMClassMock([FIRGetAccountInfoResponse class]);
+                          OCMStub([mockGetAccountInfoResponse users]).andReturn(@[
+                            mockGetAccountInfoResponseUser
+                          ]);
+                          callback(mockGetAccountInfoResponse, nil);
                         });
                       });
 
@@ -2429,11 +2443,11 @@ - (void)testlinkEmailAndRetrieveDataError {
                                         FIRFacebookAuthProviderID);
                   XCTAssertNil(error);
 
-                  OCMExpect([self->_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
-                      .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
-                                       FIRGetAccountInfoResponseCallback callback) {
-                        XCTAssertEqualObjects(request.APIKey, kAPIKey);
-                        XCTAssertEqualObjects(request.accessToken, kAccessToken);
+                  OCMExpect([self->_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]])
+                      .andCallBlock2(^(FIRSignUpNewUserRequest *_Nullable request,
+                                       FIRSignupNewUserCallback callback) {
+                        XCTAssertEqualObjects(request.email, kEmail);
+                        XCTAssertEqualObjects(request.password, kFakePassword);
                         dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
                           callback(nil, [FIRAuthErrorUtils tooManyRequestsErrorWithMessage:nil]);
                         });
@@ -2484,15 +2498,8 @@ - (void)testlinkEmailAndRetrieveDataErrorAutoSignOut {
                                         FIRFacebookAuthProviderID);
                   XCTAssertNil(error);
 
-                  OCMExpect([self->_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
-                      .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
-                                       FIRGetAccountInfoResponseCallback callback) {
-                        XCTAssertEqualObjects(request.APIKey, kAPIKey);
-                        XCTAssertEqualObjects(request.accessToken, kAccessToken);
-                        dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
-                          callback(nil, [FIRAuthErrorUtils userTokenExpiredErrorWithMessage:nil]);
-                        });
-                      });
+                  OCMExpect([self->_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]])
+                      .andDispatchError2([FIRAuthErrorUtils userTokenExpiredErrorWithMessage:nil]);
 
                   FIRAuthCredential *linkEmailCredential =
                       [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword];


Test patch: