Browse Source

Check if reverse client ID is a registered URL scheme before setting callback scheme (#7211)

* Check if reverse client ID is registered as a custom URL scheme before setting it as the callback scheme.

* Fix existing tests.

* Add tests.

* Update changelog.
Rosalyn Tan 5 years ago
parent
commit
e09c321bcd

+ 3 - 0
FirebaseAuth/CHANGELOG.md

@@ -1,3 +1,6 @@
+# Unreleased
+- [fixed] Check if the reverse client ID is configured as a custom URL scheme before setting it as the callback scheme. (#7211).
+
 # 7.3.0
 - [fixed] Catalyst browser issue with `verifyPhoneNumber` API. (#7049)
 

+ 17 - 4
FirebaseAuth/Sources/AuthProvider/OAuth/FIROAuthProvider.m

@@ -72,6 +72,12 @@ static NSString *const kCustomUrlSchemePrefix = @"app-";
       @brief The callback URL scheme used for headful-lite sign-in.
    */
   NSString *_callbackScheme;
+
+  /** @var _usingClientIDScheme
+      @brief True if the reverse client ID is registered as a custom URL scheme, and false
+     otherwise.
+   */
+  BOOL _usingClientIDScheme;
 }
 
 + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
@@ -216,9 +222,16 @@ static NSString *const kCustomUrlSchemePrefix = @"app-";
     _auth = auth;
     _providerID = providerID;
     if (_auth.app.options.clientID) {
-      _callbackScheme = [[[_auth.app.options.clientID componentsSeparatedByString:@"."]
-                             reverseObjectEnumerator].allObjects componentsJoinedByString:@"."];
-    } else {
+      NSString *reverseClientIDScheme =
+          [[[_auth.app.options.clientID componentsSeparatedByString:@"."]
+               reverseObjectEnumerator].allObjects componentsJoinedByString:@"."];
+      if ([FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:reverseClientIDScheme]) {
+        _callbackScheme = reverseClientIDScheme;
+        _usingClientIDScheme = YES;
+      }
+    }
+
+    if (!_usingClientIDScheme) {
       _callbackScheme = [kCustomUrlSchemePrefix
           stringByAppendingString:[_auth.app.options.googleAppID
                                       stringByReplacingOccurrencesOfString:@":"
@@ -304,7 +317,7 @@ static NSString *const kCustomUrlSchemePrefix = @"app-";
                                        @"eventId" : eventID,
                                        @"providerId" : strongSelf->_providerID,
                                      } mutableCopy];
-                                     if (clientID) {
+                                     if (strongSelf->_usingClientIDScheme) {
                                        urlArguments[@"clientId"] = clientID;
                                      } else {
                                        urlArguments[@"appId"] = appID;

+ 16 - 4
FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthProvider.m

@@ -104,6 +104,12 @@ extern NSString *const FIRPhoneMultiFactorID;
       @brief The callback URL scheme used for reCAPTCHA fallback.
    */
   NSString *_callbackScheme;
+
+  /** @var _usingClientIDScheme
+      @brief True if the reverse client ID is registered as a custom URL scheme, and false
+     otherwise.
+   */
+  BOOL _usingClientIDScheme;
 }
 
 /** @fn initWithAuth:
@@ -116,9 +122,15 @@ extern NSString *const FIRPhoneMultiFactorID;
   if (self) {
     _auth = auth;
     if (_auth.app.options.clientID) {
-      _callbackScheme = [[[_auth.app.options.clientID componentsSeparatedByString:@"."]
-                             reverseObjectEnumerator].allObjects componentsJoinedByString:@"."];
-    } else {
+      NSString *reverseClientIDScheme =
+          [[[_auth.app.options.clientID componentsSeparatedByString:@"."]
+               reverseObjectEnumerator].allObjects componentsJoinedByString:@"."];
+      if ([FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:reverseClientIDScheme]) {
+        _callbackScheme = reverseClientIDScheme;
+        _usingClientIDScheme = YES;
+      }
+    }
+    if (!_usingClientIDScheme) {
       _callbackScheme = [kCustomUrlSchemePrefix
           stringByAppendingString:[_auth.app.options.googleAppID
                                       stringByReplacingOccurrencesOfString:@":"
@@ -699,7 +711,7 @@ extern NSString *const FIRPhoneMultiFactorID;
                                                        value:[FIRAuthBackend authUserAgent]],
                                        [NSURLQueryItem queryItemWithName:@"eventId" value:eventID]
                                      ] mutableCopy];
-                                     if (clientID) {
+                                     if (self->_usingClientIDScheme) {
                                        [queryItems
                                            addObject:[NSURLQueryItem queryItemWithName:@"clientId"
                                                                                  value:clientID]];

+ 128 - 24
FirebaseAuth/Tests/Unit/FIROAuthProviderTests.m

@@ -268,9 +268,6 @@ static NSString *const kUnknownErrorString =
     @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion:
  */
 - (void)testGetCredentialWithUIDelegateWithClientID {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -278,6 +275,9 @@ static NSString *const kUnknownErrorString =
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
+
   OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
       .andCallBlock2(
           ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
@@ -371,9 +371,6 @@ static NSString *const kUnknownErrorString =
         cancelation.
  */
 - (void)testGetCredentialWithUIDelegateUserCancellationWithClientID {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -381,6 +378,9 @@ static NSString *const kUnknownErrorString =
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
+
   OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
       .andCallBlock2(
           ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
@@ -471,9 +471,6 @@ static NSString *const kUnknownErrorString =
         failed network request within the web context.
  */
 - (void)testGetCredentialWithUIDelegateNetworkRequestFailedWithClientID {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -481,6 +478,9 @@ static NSString *const kUnknownErrorString =
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
+
   OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
       .andCallBlock2(
           ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
@@ -569,9 +569,6 @@ static NSString *const kUnknownErrorString =
         internal error within the web context.
  */
 - (void)testGetCredentialWithUIDelegateInternalErrorWithClientID {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -579,6 +576,9 @@ static NSString *const kUnknownErrorString =
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
+
   OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
       .andCallBlock2(
           ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
@@ -668,9 +668,6 @@ static NSString *const kUnknownErrorString =
         use of an invalid client ID.
  */
 - (void)testGetCredentialWithUIDelegateInvalidClientID {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -678,6 +675,9 @@ static NSString *const kUnknownErrorString =
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
+
   OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
       .andCallBlock2(
           ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
@@ -767,9 +767,6 @@ static NSString *const kUnknownErrorString =
         unknown error.
  */
 - (void)testGetCredentialWithUIDelegateUnknownErrorWithClientID {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -777,6 +774,9 @@ static NSString *const kUnknownErrorString =
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
+
   OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
       .andCallBlock2(
           ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
@@ -866,8 +866,109 @@ static NSString *const kUnknownErrorString =
     @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion:
  */
 - (void)testGetCredentialWithUIDelegateWithFirebaseAppID {
+  id mockBundle = OCMClassMock([NSBundle class]);
+  OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
+  OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
+    @{@"CFBundleURLSchemes" : @[ kFakeEncodedFirebaseAppID ]}
+  ]);
+  OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
+
   _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
 
+  OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
+      .andCallBlock2(
+          ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
+            XCTAssertNotNil(request);
+            dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+              id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
+              OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
+                kFakeAuthorizedDomain
+              ]);
+              callback(mockGetProjectConfigResponse, nil);
+            });
+          });
+
+  id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
+
+  // Expect view controller presentation by UIDelegate.
+  OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
+                               UIDelegate:mockUIDelegate
+                          callbackMatcher:OCMOCK_ANY
+                               completion:OCMOCK_ANY])
+      .andDo(^(NSInvocation *invocation) {
+        __unsafe_unretained id unretainedArgument;
+        // Indices 0 and 1 indicate the hidden arguments self and _cmd.
+        // `presentURL` is at index 2.
+        [invocation getArgument:&unretainedArgument atIndex:2];
+        NSURL *presentURL = unretainedArgument;
+        XCTAssertEqualObjects(presentURL.scheme, @"https");
+        XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
+        XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
+        NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
+        XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
+        XCTAssertEqualObjects(params[@"appId"], kFakeFirebaseAppID);
+        XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
+        XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
+        XCTAssertNotNil(params[@"v"]);
+        // `callbackMatcher` is at index 4
+        [invocation getArgument:&unretainedArgument atIndex:4];
+        FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
+        NSMutableString *redirectURL = [NSMutableString
+            stringWithString:[kFakeEncodedFirebaseAppID
+                                 stringByAppendingString:kFakeRedirectURLResponseURL]];
+        // Add fake OAuthResponse to callback.
+        [redirectURL appendString:kFakeOAuthResponseURL];
+        // Verify that the URL is rejected by the callback matcher without the event ID.
+        XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
+        [redirectURL appendString:@"%26eventId%3D"];
+        [redirectURL appendString:params[@"eventId"]];
+        NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
+        // Verify that the URL is accepted by the callback matcher with the matching event ID.
+        XCTAssertTrue(callbackMatcher([originalComponents URL]));
+        NSURLComponents *components = [originalComponents copy];
+        components.query = @"https";
+        XCTAssertFalse(callbackMatcher([components URL]));
+        components = [originalComponents copy];
+        components.host = @"badhost";
+        XCTAssertFalse(callbackMatcher([components URL]));
+        components = [originalComponents copy];
+        components.path = @"badpath";
+        XCTAssertFalse(callbackMatcher([components URL]));
+        components = [originalComponents copy];
+        components.query = @"badquery";
+        XCTAssertFalse(callbackMatcher([components URL]));
+
+        // `completion` is at index 5
+        [invocation getArgument:&unretainedArgument atIndex:5];
+        FIRAuthURLPresentationCompletion completion = unretainedArgument;
+        dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+          completion(originalComponents.URL, nil);
+        });
+      });
+
+  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+  [_provider
+      getCredentialWithUIDelegate:mockUIDelegate
+                       completion:^(FIRAuthCredential *_Nullable credential,
+                                    NSError *_Nullable error) {
+                         XCTAssertTrue([NSThread isMainThread]);
+                         XCTAssertNil(error);
+                         XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
+                         FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
+                         XCTAssertEqualObjects(kFakeOAuthResponseURL,
+                                               OAuthCredential.OAuthResponseURLString);
+                         [expectation fulfill];
+                       }];
+  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+  OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testGetCredentialWithUIDelegateWithFirebaseAppIDWhileClientIdPresent
+    @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion: when the
+   client ID is present in the plist file, but the encoded app ID is the registered custom URL
+   scheme.
+ */
+- (void)testGetCredentialWithUIDelegateWithFirebaseAppIDWhileClientIdPresent {
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -875,6 +976,9 @@ static NSString *const kUnknownErrorString =
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
+
   OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
       .andCallBlock2(
           ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
@@ -968,12 +1072,6 @@ static NSString *const kUnknownErrorString =
    emulator.
  */
 - (void)testGetCredentialWithUIDelegateUseEmulator {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  NSString *emulatorHostAndPort =
-      [NSString stringWithFormat:@"%@:%@", kFakeEmulatorHost, kFakeEmulatorPort];
-  OCMStub([_mockRequestConfiguration emulatorHostAndPort]).andReturn(emulatorHostAndPort);
-  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -981,6 +1079,12 @@ static NSString *const kUnknownErrorString =
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  NSString *emulatorHostAndPort =
+      [NSString stringWithFormat:@"%@:%@", kFakeEmulatorHost, kFakeEmulatorPort];
+  OCMStub([_mockRequestConfiguration emulatorHostAndPort]).andReturn(emulatorHostAndPort);
+  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
+
   id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
 
   // Expect view controller presentation by UIDelegate.

+ 187 - 51
FirebaseAuth/Tests/Unit/FIRPhoneAuthProviderTests.m

@@ -324,9 +324,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
         number was provided.
  */
 - (void)testVerifyEmptyPhoneNumber {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -334,6 +331,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   // Empty phone number is checked on the client side so no backend RPC is mocked.
   XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
   [_provider verifyPhoneNumber:@""
@@ -351,9 +351,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
         number was provided.
  */
 - (void)testVerifyInvalidPhoneNumber {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -361,6 +358,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
       .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
         callback(YES);
@@ -397,9 +397,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
     @brief Tests a successful invocation of @c verifyPhoneNumber:completion:.
  */
 - (void)testVerifyPhoneNumber {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -407,6 +404,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
       .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
         callback(YES);
@@ -447,9 +447,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
         is disabled.
  */
 - (void)testVerifyPhoneNumberInTestMode {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -457,6 +454,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   // Disable app verification.
   FIRAuthSettings *settings = [[FIRAuthSettings alloc] init];
   settings.appVerificationDisabledForTesting = YES;
@@ -499,9 +499,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
         is disabled.
  */
 - (void)testVerifyPhoneNumberInTestModeFailure {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -509,6 +506,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   // Disable app verification.
   FIRAuthSettings *settings = [[FIRAuthSettings alloc] init];
   settings.appVerificationDisabledForTesting = YES;
@@ -548,8 +548,140 @@ static const NSTimeInterval kExpectationTimeout = 2;
     @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion:.
  */
 - (void)testVerifyPhoneNumberUIDelegateFirebaseAppIdFlow {
+  id mockBundle = OCMClassMock([NSBundle class]);
+  OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
+  OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
+    @{@"CFBundleURLSchemes" : @[ kFakeEncodedFirebaseAppID ]}
+  ]);
+  OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
+
   _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
 
+  // Simulate missing app token error.
+  OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
+      .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
+        callback(YES);
+      });
+  OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
+  OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
+      .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
+        NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
+                                             code:FIRAuthErrorCodeMissingAppToken
+                                         userInfo:nil];
+        callback(nil, error);
+      });
+  OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
+      .andCallBlock2(
+          ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
+            XCTAssertNotNil(request);
+            dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+              id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
+              OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
+                kFakeAuthorizedDomain
+              ]);
+              callback(mockGetProjectConfigResponse, nil);
+            });
+          });
+  id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
+
+  // Expect view controller presentation by UIDelegate.
+  OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
+                               UIDelegate:mockUIDelegate
+                          callbackMatcher:OCMOCK_ANY
+                               completion:OCMOCK_ANY])
+      .andDo(^(NSInvocation *invocation) {
+        __unsafe_unretained id unretainedArgument;
+        // Indices 0 and 1 indicate the hidden arguments self and _cmd.
+        // `presentURL` is at index 2.
+        [invocation getArgument:&unretainedArgument atIndex:2];
+        NSURL *presentURL = unretainedArgument;
+        XCTAssertEqualObjects(presentURL.scheme, @"https");
+        XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
+        XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
+
+        NSURLComponents *actualURLComponents = [NSURLComponents componentsWithURL:presentURL
+                                                          resolvingAgainstBaseURL:NO];
+        NSArray<NSURLQueryItem *> *queryItems = [actualURLComponents queryItems];
+        XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"ibi" from:queryItems],
+                              kFakeBundleID);
+        XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"appId" from:queryItems],
+                              kFakeFirebaseAppID);
+        XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"apiKey" from:queryItems],
+                              kFakeAPIKey);
+        XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"authType" from:queryItems],
+                              @"verifyApp");
+        XCTAssertNotNil([FIRAuthWebUtils queryItemValue:@"v" from:queryItems]);
+        // `callbackMatcher` is at index 4
+        [invocation getArgument:&unretainedArgument atIndex:4];
+        FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
+        NSMutableString *redirectURL = [NSMutableString
+            stringWithString:[kFakeEncodedFirebaseAppID
+                                 stringByAppendingString:kFakeRedirectURLStringWithReCAPTCHAToken]];
+        // Verify that the URL is rejected by the callback matcher without the event ID.
+        XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
+        [redirectURL appendString:@"%26eventId%3D"];
+        [redirectURL appendString:[FIRAuthWebUtils queryItemValue:@"eventId" from:queryItems]];
+        NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
+        // Verify that the URL is accepted by the callback matcher with the matching event ID.
+        XCTAssertTrue(callbackMatcher([originalComponents URL]));
+        NSURLComponents *components = [originalComponents copy];
+        components.query = @"https";
+        XCTAssertFalse(callbackMatcher([components URL]));
+        components = [originalComponents copy];
+        components.host = @"badhost";
+        XCTAssertFalse(callbackMatcher([components URL]));
+        components = [originalComponents copy];
+        components.path = @"badpath";
+        XCTAssertFalse(callbackMatcher([components URL]));
+        components = [originalComponents copy];
+        components.query = @"badquery";
+        XCTAssertFalse(callbackMatcher([components URL]));
+
+        // `completion` is at index 5
+        [invocation getArgument:&unretainedArgument atIndex:5];
+        FIRAuthURLPresentationCompletion completion = unretainedArgument;
+        dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+          completion([NSURL URLWithString:[kFakeEncodedFirebaseAppID
+                                              stringByAppendingString:
+                                                  kFakeRedirectURLStringWithReCAPTCHAToken]],
+                     nil);
+        });
+      });
+
+  OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
+      .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
+                       FIRSendVerificationCodeResponseCallback callback) {
+        XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
+        XCTAssertNil(request.appCredential);
+        XCTAssertEqualObjects(request.reCAPTCHAToken, kFakeReCAPTCHAToken);
+        dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+          id mockSendVerificationCodeResponse =
+              OCMClassMock([FIRSendVerificationCodeResponse class]);
+          OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
+          callback(mockSendVerificationCodeResponse, nil);
+        });
+      });
+
+  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+  [_provider verifyPhoneNumber:kTestPhoneNumber
+                    UIDelegate:mockUIDelegate
+                    completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
+                      XCTAssertTrue([NSThread isMainThread]);
+                      XCTAssertNil(error);
+                      XCTAssertEqualObjects(verificationID, kTestVerificationID);
+                      [expectation fulfill];
+                    }];
+  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+  OCMVerifyAll(_mockBackend);
+  OCMVerifyAll(_mockNotificationManager);
+}
+
+/** @fn testVerifyPhoneNumberUIDelegateFirebaseAppIdWhileClientIdPresentFlow
+    @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion: when the
+   client ID is present in the plist file, but the encoded app ID is the registered custom URL
+   scheme.
+ */
+- (void)testVerifyPhoneNumberUIDelegateFirebaseAppIdWhileClientIdPresentFlow {
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -557,6 +689,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   // Simulate missing app token error.
   OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
       .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
@@ -680,9 +815,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
     @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion:.
  */
 - (void)testVerifyPhoneNumberUIDelegateClientIdFlow {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -690,6 +822,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   // Simulate missing app token error.
   OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
       .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
@@ -814,9 +949,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
         invalid client ID error.
  */
 - (void)testVerifyPhoneNumberUIDelegateInvalidClientID {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -824,6 +956,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   // Simulate missing app token error.
   OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
       .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
@@ -886,9 +1021,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
         network request failed error.
  */
 - (void)testVerifyPhoneNumberUIDelegateNetworkRequestFailed {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -896,6 +1028,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   // Simulate missing app token error.
   OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
       .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
@@ -958,9 +1093,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
         internal error.
  */
 - (void)testVerifyPhoneNumberUIDelegateWebInternalError {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -968,6 +1100,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   // Simulate missing app token error.
   OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
       .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
@@ -1030,9 +1165,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
         invalid client ID.
  */
 - (void)testVerifyPhoneNumberUIDelegateUnexpectedError {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -1040,6 +1172,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   // Simulate missing app token error.
   OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
       .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
@@ -1104,9 +1239,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
         structure of the error response.
  */
 - (void)testVerifyPhoneNumberUIDelegateUnstructuredError {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -1114,6 +1246,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   // Simulate missing app token error.
   OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
       .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
@@ -1177,14 +1312,15 @@ static const NSTimeInterval kExpectationTimeout = 2;
         exception.
  */
 - (void)testVerifyPhoneNumberUIDelegateRaiseException {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
     @{@"CFBundleURLSchemes" : @[ @"badscheme" ]}
   ]);
+
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
   XCTAssertThrows([_provider
       verifyPhoneNumber:kTestPhoneNumber
@@ -1198,9 +1334,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
     @brief Tests returning an error for the app failing to forward notification.
  */
 - (void)testNotForwardingNotification {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -1208,6 +1341,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
       .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
         callback(NO);
@@ -1229,9 +1365,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
     @brief Tests returning an error for the app failing to provide an APNS device token.
  */
 - (void)testMissingAPNSToken {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -1239,6 +1372,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   // Simulate missing app token error.
   OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
       .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
@@ -1302,9 +1438,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
     @brief Tests verifying client before sending verification code.
  */
 - (void)testVerifyClient {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -1312,6 +1445,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
       .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
         callback(YES);
@@ -1386,9 +1522,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
     @brief Tests failed retry after failing to send verification code.
  */
 - (void)testSendVerificationCodeFailedRetry {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -1396,6 +1529,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
       .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
         callback(YES);
@@ -1491,9 +1627,6 @@ static const NSTimeInterval kExpectationTimeout = 2;
     @brief Tests successful retry after failing to send verification code.
  */
 - (void)testSendVerificationCodeSuccessFulRetry {
-  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
-  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
-
   id mockBundle = OCMClassMock([NSBundle class]);
   OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
   OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@@ -1501,6 +1634,9 @@ static const NSTimeInterval kExpectationTimeout = 2;
   ]);
   OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
 
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+
   OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
       .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
         callback(YES);