-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[Auth] Fix handling of AppCheck placeholder tokens #15555
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
Closed
+64
−10
Closed
Changes from 2 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| 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. | ||
| */ | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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] = [] | ||
|
|
@@ -807,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 | ||
|
|
@@ -821,7 +823,7 @@ | |
| XCTAssertTrue(reCAPTCHAfallback) | ||
| XCTAssertEqual(token, self.kFakeReCAPTCHAToken) | ||
| case .empty: | ||
| XCTAssertTrue(testMode) | ||
| XCTFail("Should not be empty") | ||
| } | ||
| requestExpectation.fulfill() | ||
| do { | ||
|
|
@@ -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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| fakeAppCheck.error = expectedError | ||
| try await internalTestVerify( | ||
| errorCode: expectedError.code, | ||
| function: #function, | ||
| reCAPTCHAfallback: true, | ||
| presenterError: nil, | ||
| fakeAppCheck: fakeAppCheck | ||
| ) | ||
| } | ||
| } | ||
| #endif | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewers: Do you agree with Gemini's answer to this question?
✦ That's an excellent question, and it highlights a key nuance of this fix.
No, this fix is not ideal for apps that include App Check but do not have enforcement turned on.
Here's the breakdown of the behavior change:
not enforcing App Check, the backend would ignore the invalid token and the authentication would succeed.
is never sent to the Auth backend.
Impact on Your Scenario:
For the ~1% of users experiencing a token generation failure on the first launch, their sign-up will now fail immediately, even with enforcement turned off. Previously, it
would have succeeded.
Why This Fix is Still Correct (From an SDK Perspective):
The new behavior is more explicit and predictable. Silently sending invalid "dummy" tokens is a bug because it masks an underlying problem—the token generation failure. The
"fail-fast" approach is generally safer because it makes the SDK's behavior consistent, regardless of your backend settings.
Recommended Solution for Your Use Case:
The best practice to handle these transient, first-launch errors is to implement a retry mechanism in your application code.
When you call auth.createUser(...) or provider.verifyPhoneNumber(...), your error handling logic should catch the App Check-related error and prompt the user to try again. A
simple "An error occurred, please try again" message with a retry button is often sufficient. The token generation process is highly likely to have succeeded by the time the
user initiates the second attempt.
This approach provides a much better user experience and makes your app resilient to these initial, temporary failures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This definitely concerns me because it seems to defeat the purpose of the difference between the public FirebaseAppCheck API and the FirebaseAppCheckInterop APIs. Let's run this by @weixifan before merging.