From 9d61ab3036072626405dfd94d547cf49b95ad32b Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 27 Nov 2025 15:34:59 -0800 Subject: [PATCH 1/3] [Auth] Fix handling of AppCheck placeholder tokens --- FirebaseAuth/CHANGELOG.md | 1 + .../Swift/AuthProvider/OAuthProvider.swift | 4 +-- .../AuthProvider/PhoneAuthProvider.swift | 4 +-- .../Tests/Unit/Fakes/FakeAppCheck.swift | 6 ++-- .../Tests/Unit/OAuthProviderTests.swift | 32 +++++++++++++++++++ .../Tests/Unit/PhoneAuthProviderTests.swift | 25 +++++++++++++-- 6 files changed, 63 insertions(+), 9 deletions(-) diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md index c1dfc40947a..f687abe3535 100644 --- a/FirebaseAuth/CHANGELOG.md +++ b/FirebaseAuth/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased - [fixed] Add a mechanism to prevent concurrent token refreshes. (#15474) +- [fixed] Fix handling of AppCheck placeholder tokens. (#15372) # 12.2.0 - [added] Added TOTP support for macOS. diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift index 8dd2b00b99a..b77cf20398e 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift @@ -435,8 +435,8 @@ import Foundation let tokenResult = await appCheck.getToken(forcingRefresh: false) if let error = tokenResult.error { AuthLog.logWarning(code: "I-AUT000018", - message: "Error getting App Check token; using placeholder " + - "token instead. Error: \(error)") + message: "Error getting App Check token. Error: \(error)") + throw error } let appCheckTokenFragment = "fac=\(tokenResult.token)" components?.fragment = appCheckTokenFragment diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift index c0395182268..bdee09d667e 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift @@ -627,8 +627,8 @@ import Foundation let tokenResult = await appCheck.getToken(forcingRefresh: false) if let error = tokenResult.error { AuthLog.logWarning(code: "I-AUT000018", - message: "Error getting App Check token; using placeholder " + - "token instead. Error: \(error)") + message: "Error getting App Check token. Error: \(error)") + throw error } let appCheckTokenFragment = "fac=\(tokenResult.token)" components?.fragment = appCheckTokenFragment diff --git a/FirebaseAuth/Tests/Unit/Fakes/FakeAppCheck.swift b/FirebaseAuth/Tests/Unit/Fakes/FakeAppCheck.swift index 6b000e05aa0..c00a05b0002 100644 --- a/FirebaseAuth/Tests/Unit/Fakes/FakeAppCheck.swift +++ b/FirebaseAuth/Tests/Unit/Fakes/FakeAppCheck.swift @@ -18,9 +18,10 @@ import Foundation class FakeAppCheck: NSObject, AppCheckInterop { let fakeAppCheckToken = "fakeAppCheckToken" + var error: Error? func getToken(forcingRefresh: Bool, completion: @escaping AppCheckTokenHandlerInterop) { - completion(FakeAppCheckResult(token: "fakeAppCheckToken")) + completion(FakeAppCheckResult(token: "fakeAppCheckToken", error: error)) } func tokenDidChangeNotificationName() -> String { @@ -40,7 +41,8 @@ class FakeAppCheckResult: NSObject, FIRAppCheckTokenResultInterop { var token: String var error: Error? - init(token: String) { + init(token: String, error: Error? = nil) { self.token = token + self.error = error } } diff --git a/FirebaseAuth/Tests/Unit/OAuthProviderTests.swift b/FirebaseAuth/Tests/Unit/OAuthProviderTests.swift index d4b2b47d6f2..c5cca11c1f9 100644 --- a/FirebaseAuth/Tests/Unit/OAuthProviderTests.swift +++ b/FirebaseAuth/Tests/Unit/OAuthProviderTests.swift @@ -229,6 +229,38 @@ import FirebaseCore OAuthProviderTests.testAppCheck = false } + func testGetCredentialWithUIDelegateWithAppCheckError() throws { + let expectation = self.expectation(description: "App Check error propagated") + let fakeAppCheck = FakeAppCheck() + let expectedError = NSError(domain: "appCheckError", code: -1) + fakeAppCheck.error = expectedError + initApp(#function, useAppID: true) + let auth = try XCTUnwrap(OAuthProviderTests.auth) + auth.requestConfiguration.appCheck = fakeAppCheck + let provider = OAuthProvider(providerID: kFakeProviderID, auth: auth) + + let projectConfigExpectation = self.expectation(description: "projectConfiguration") + rpcIssuer.projectConfigRequester = { request in + projectConfigExpectation.fulfill() + do { + return try self.rpcIssuer.respond(withJSON: ["authorizedDomains": [ + Self.kFakeAuthorizedWebDomain, + Self.kFakeAuthorizedDomain, + ]]) + } catch { + XCTFail("Failure sending response: \(error)") + return (nil, nil) + } + } + + provider.getCredentialWith(nil) { credential, error in + XCTAssertNil(credential) + XCTAssertEqual(error as? NSError, expectedError) + expectation.fulfill() + } + waitForExpectations(timeout: 2.0) + } + /** @fn testOAuthCredentialCoding @brief Tests successful archiving and unarchiving of @c GoogleAuthCredential. */ diff --git a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift index e5a3e3bf766..2a9640ece39 100644 --- a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift +++ b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift @@ -762,10 +762,12 @@ bothClientAndAppID: Bool = false, reCAPTCHAfallback: Bool = false, forwardingNotification: Bool = true, - presenterError: Error? = nil) async throws { + presenterError: Error? = nil, + fakeAppCheck: FakeAppCheck? = nil) async throws { initApp(function, useClientID: useClientID, bothClientAndAppID: bothClientAndAppID, testMode: testMode, - forwardingNotification: forwardingNotification) + forwardingNotification: forwardingNotification, + fakeAppCheck: fakeAppCheck) let auth = try XCTUnwrap(PhoneAuthProviderTests.auth) let provider = PhoneAuthProvider.provider(auth: auth) var expectations: [XCTestExpectation] = [] @@ -861,7 +863,8 @@ bothClientAndAppID: Bool = false, testMode: Bool = false, forwardingNotification: Bool = true, - fakeToken: Bool = false) { + fakeToken: Bool = false, + fakeAppCheck: FakeAppCheck? = nil) { let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000", gcmSenderID: "00000000000000000-00000000000-000000000") options.apiKey = PhoneAuthProviderTests.kFakeAPIKey @@ -884,6 +887,9 @@ kAuthGlobalWorkQueue.sync { // Wait for Auth protectedDataInitialization to finish. PhoneAuthProviderTests.auth = auth + if let fakeAppCheck = fakeAppCheck { + auth.requestConfiguration.appCheck = fakeAppCheck + } if testMode { // Disable app verification. let settings = AuthSettings() @@ -1095,5 +1101,18 @@ "://firebaseauth/" + "link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcallback%3FauthType%" + "3DverifyApp%26recaptchaToken%3DfakeReCAPTCHAToken" + + func testVerifyPhoneNumberWithAppCheckError() async throws { + let fakeAppCheck = FakeAppCheck() + let expectedError = NSError(domain: "appCheckError", code: -1) + fakeAppCheck.error = expectedError + try await internalTestVerify( + errorCode: expectedError.code, + function: #function, + reCAPTCHAfallback: true, + presenterError: nil, + fakeAppCheck: fakeAppCheck + ) + } } #endif From 250ad968704a6820633e026214a255d07101c9b9 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 27 Nov 2025 16:27:53 -0800 Subject: [PATCH 2/3] fix flake in new test --- FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift index 2a9640ece39..37416c288e9 100644 --- a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift +++ b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift @@ -809,7 +809,7 @@ presenterError: presenterError ) } - if errorURLString == nil, presenterError == nil { + if errorURLString == nil, presenterError == nil, fakeAppCheck?.error == nil { let requestExpectation = expectation(description: "verifyRequester") expectations.append(requestExpectation) rpcIssuer.verifyRequester = { request in @@ -823,7 +823,7 @@ XCTAssertTrue(reCAPTCHAfallback) XCTAssertEqual(token, self.kFakeReCAPTCHAToken) case .empty: - XCTAssertTrue(testMode) + XCTFail("Should not be empty") } requestExpectation.fulfill() do { From 0d983df6fd8dd8dd70358667f1b19047bcee15ba Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 28 Nov 2025 07:31:11 -0800 Subject: [PATCH 3/3] fix flake --- FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift index 37416c288e9..d7b0b109137 100644 --- a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift +++ b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift @@ -823,7 +823,7 @@ XCTAssertTrue(reCAPTCHAfallback) XCTAssertEqual(token, self.kFakeReCAPTCHAToken) case .empty: - XCTFail("Should not be empty") + XCTAssertTrue(testMode) } requestExpectation.fulfill() do {