Skip to content

Commit dea18e9

Browse files
authored
End-to-End UI Tests in Authentication Example for Passkey (#15212)
1 parent d0a0243 commit dea18e9

File tree

4 files changed

+534
-259
lines changed

4 files changed

+534
-259
lines changed

FirebaseAuth/Tests/SampleSwift/AuthenticationExampleUITests/AuthenticationExampleUITests.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,40 @@ class AuthenticationExampleUITests: XCTestCase {
309309
)
310310
}
311311

312+
#if os(iOS) || os(tvOS) || os(macOS)
313+
@available(iOS 15.0, macOS 12.0, tvOS 16.0, *)
314+
func testPasskeyList() {
315+
signOut()
316+
let testEmail = "sample.ios.auth@gmail.com"
317+
let testPassword = "sample.ios.auth"
318+
let testPasskeyName = "sampleiosauth"
319+
app.staticTexts["Email & Password Login"].tap()
320+
app.textFields["Email"].tap()
321+
app.textFields["Email"].typeText(testEmail)
322+
app.textFields["Password"].tap()
323+
app.textFields["Password"].typeText(testPassword)
324+
app.buttons["Login"].tap()
325+
wait(forElement: app.navigationBars["User"], timeout: 5.0)
326+
XCTAssertTrue(app.navigationBars["User"].exists)
327+
XCTAssertTrue(
328+
app.staticTexts[testEmail].exists,
329+
"The user should be signed in and the email field should display their email."
330+
)
331+
let userTable = app.tables.firstMatch
332+
XCTAssertTrue(userTable.waitForExistence(timeout: 5.0), "User detail list should exist")
333+
let passkeyLabel = userTable.staticTexts[testPasskeyName]
334+
if !passkeyLabel.exists {
335+
for _ in 0 ..< 5 where !passkeyLabel.exists {
336+
userTable.swipeUp()
337+
}
338+
}
339+
XCTAssertTrue(
340+
passkeyLabel.waitForExistence(timeout: 5.0),
341+
"Passkey named '\(testPasskeyName)' should be visible in the Passkeys section."
342+
)
343+
}
344+
#endif
345+
312346
// MARK: - Private Helpers
313347

314348
private func signOut() {
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#if os(iOS) || os(tvOS) || os(macOS)
18+
19+
import AuthenticationServices
20+
@testable import FirebaseAuth
21+
import Foundation
22+
import XCTest
23+
24+
@available(iOS 15.0, macOS 12.0, tvOS 16.0, *)
25+
class PasskeyTests: TestsBase {
26+
@available(iOS 15.0, macOS 12.0, tvOS 16.0, *)
27+
func testStartPasskeyEnrollmentResponseSuccess() async throws {
28+
try await signInAnonymouslyAsync()
29+
guard let user = Auth.auth().currentUser else {
30+
XCTFail("Expected a signed-in user")
31+
return
32+
}
33+
try? await user.reload()
34+
let request = try await user.startPasskeyEnrollment(withName: "Test1Passkey")
35+
XCTAssertFalse(request.relyingPartyIdentifier.isEmpty, "rpID should be non-empty")
36+
XCTAssertFalse(request.challenge.isEmpty, "challenge should be non-empty")
37+
XCTAssertNotNil(request.userID, "userID should be present")
38+
XCTAssertNotNil(request as ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest)
39+
try? await deleteCurrentUserAsync()
40+
}
41+
42+
@available(iOS 15.0, macOS 12.0, tvOS 16.0, *)
43+
func testStartPasskeyEnrollmentFailureWithInvalidToken() async throws {
44+
try await signInAnonymouslyAsync()
45+
guard let user = Auth.auth().currentUser else {
46+
XCTFail("Expected a signed-in user")
47+
return
48+
}
49+
let config = user.requestConfiguration
50+
let token = "invalidToken"
51+
let badRequest = StartPasskeyEnrollmentRequest(idToken: token, requestConfiguration: config)
52+
do {
53+
_ = try await user.backend.call(with: badRequest)
54+
XCTFail("Expected .invalidUserToken")
55+
} catch {
56+
let ns = error as NSError
57+
if let code = AuthErrorCode(rawValue: ns.code) {
58+
XCTAssertEqual(code, .invalidUserToken, "Expected .invalidUserToken, got \(code)")
59+
} else {
60+
XCTFail("Unexpected error: \(error)")
61+
}
62+
let message = (ns.userInfo[NSLocalizedDescriptionKey] as? String ?? "").uppercased()
63+
XCTAssertTrue(
64+
message
65+
.contains(
66+
"THIS USER'S CREDENTIAL ISN'T VALID FOR THIS PROJECT. THIS CAN HAPPEN IF THE USER'S TOKEN HAS BEEN TAMPERED WITH, OR IF THE USER DOESN’T BELONG TO THE PROJECT ASSOCIATED WITH THE API KEY USED IN YOUR REQUEST."
67+
),
68+
"Expected invalidUserToken, got: \(message)"
69+
)
70+
}
71+
}
72+
73+
@available(iOS 15.0, macOS 12.0, tvOS 16.0, *)
74+
func testFinalizePasskeyEnrollmentFailureWithInvalidToken() async throws {
75+
try await signInAnonymouslyAsync()
76+
guard let user = Auth.auth().currentUser else {
77+
XCTFail("Expected a signed-in user")
78+
return
79+
}
80+
let badRequest = FinalizePasskeyEnrollmentRequest(
81+
idToken: "invalidToken",
82+
name: "fakeName",
83+
credentialID: "fakeCredentialId".data(using: .utf8)!.base64EncodedString(),
84+
clientDataJSON: "fakeClientData".data(using: .utf8)!.base64EncodedString(),
85+
attestationObject: "fakeAttestion".data(using: .utf8)!.base64EncodedString(),
86+
requestConfiguration: user.requestConfiguration
87+
)
88+
do {
89+
_ = try await user.backend.call(with: badRequest)
90+
XCTFail("Expected invalid_user_token")
91+
} catch {
92+
let ns = error as NSError
93+
if let code = AuthErrorCode(rawValue: ns.code) {
94+
XCTAssertEqual(code, .invalidUserToken, "Expected .invalidUserToken, got \(code)")
95+
}
96+
let message = (ns.userInfo[NSLocalizedDescriptionKey] as? String ?? "").uppercased()
97+
XCTAssertTrue(
98+
message
99+
.contains(
100+
"THIS USER'S CREDENTIAL ISN'T VALID FOR THIS PROJECT. THIS CAN HAPPEN IF THE USER'S TOKEN HAS BEEN TAMPERED WITH, OR IF THE USER DOESN’T BELONG TO THE PROJECT ASSOCIATED WITH THE API KEY USED IN YOUR REQUEST."
101+
),
102+
"Expected invalidUserToken, got: \(message)"
103+
)
104+
}
105+
try? await deleteCurrentUserAsync()
106+
}
107+
108+
@available(iOS 15.0, macOS 12.0, tvOS 16.0, *)
109+
func testFinalizePasskeyEnrollmentFailureWithoutAttestation() async throws {
110+
try await signInAnonymouslyAsync()
111+
guard let user = Auth.auth().currentUser else {
112+
XCTFail("Expected a signed-in user")
113+
return
114+
}
115+
try? await user.reload()
116+
let token = user.rawAccessToken()
117+
let badRequest = FinalizePasskeyEnrollmentRequest(
118+
idToken: token,
119+
name: "fakeName",
120+
credentialID: "fakeCredentialId".data(using: .utf8)!.base64EncodedString(),
121+
clientDataJSON: "fakeClientData".data(using: .utf8)!.base64EncodedString(),
122+
attestationObject: "fakeAttestion".data(using: .utf8)!.base64EncodedString(),
123+
requestConfiguration: user.requestConfiguration
124+
)
125+
do {
126+
_ = try await user.backend.call(with: badRequest)
127+
XCTFail("Expected invalid_authenticator_response")
128+
} catch {
129+
let ns = error as NSError
130+
if let code = AuthErrorCode(rawValue: ns.code) {
131+
XCTAssertEqual(code, .invalidAuthenticatorResponse,
132+
"Expected .invalidAuthenticatorResponse, got \(code)")
133+
}
134+
let message = (ns.userInfo[NSLocalizedDescriptionKey] as? String ?? "").uppercased()
135+
XCTAssertTrue(
136+
message
137+
.contains(
138+
"DURING PASSKEY ENROLLMENT AND SIGN IN, THE AUTHENTICATOR RESPONSE IS NOT PARSEABLE, MISSING REQUIRED FIELDS, OR CERTAIN FIELDS ARE INVALID VALUES THAT COMPROMISE THE SECURITY OF THE SIGN-IN OR ENROLLMENT."
139+
),
140+
"Expected INVALID_AUTHENTICATOR_RESPONSE, got: \(message)"
141+
)
142+
}
143+
try? await deleteCurrentUserAsync()
144+
}
145+
146+
@available(iOS 15.0, macOS 12.0, tvOS 16.0, *)
147+
func testStartPasskeySignInSuccess() async throws {
148+
let assertionRequest = try await Auth.auth().startPasskeySignIn()
149+
XCTAssertFalse(assertionRequest.relyingPartyIdentifier.isEmpty,
150+
"rpID should be non-empty")
151+
XCTAssertFalse(assertionRequest.challenge.isEmpty,
152+
"challenge should be non-empty")
153+
XCTAssertNotNil(
154+
assertionRequest as ASAuthorizationPlatformPublicKeyCredentialAssertionRequest
155+
)
156+
}
157+
158+
@available(iOS 15.0, macOS 12.0, tvOS 16.0, *)
159+
func testFinalizePasskeySignInFailureInvalidAttestation() async throws {
160+
let auth = Auth.auth()
161+
let config = auth.requestConfiguration
162+
let badRequest = FinalizePasskeySignInRequest(
163+
credentialID: "fakeCredentialId".data(using: .utf8)!.base64EncodedString(),
164+
clientDataJSON: "fakeClientData".data(using: .utf8)!.base64EncodedString(),
165+
authenticatorData: "fakeAuthenticatorData".data(using: .utf8)!.base64EncodedString(),
166+
signature: "fakeSignature".data(using: .utf8)!.base64EncodedString(),
167+
userId: "fakeUID".data(using: .utf8)!.base64EncodedString(),
168+
requestConfiguration: config
169+
)
170+
do {
171+
_ = try await auth.backend.call(with: badRequest)
172+
} catch {
173+
let ns = error as NSError
174+
if let code = AuthErrorCode(rawValue: ns.code) {
175+
XCTAssertEqual(code, .userNotFound)
176+
}
177+
}
178+
}
179+
180+
@available(iOS 15.0, macOS 12.0, tvOS 16.0, *)
181+
func testFinalizePasskeySignInFailureIncorrectAttestation() async throws {
182+
let auth = Auth.auth()
183+
let config = auth.requestConfiguration
184+
let badRequest = FinalizePasskeySignInRequest(
185+
credentialID: "",
186+
clientDataJSON: "",
187+
authenticatorData: "",
188+
signature: "",
189+
userId: "",
190+
requestConfiguration: config
191+
)
192+
do {
193+
_ = try await auth.backend.call(with: badRequest)
194+
} catch {
195+
let ns = error as NSError
196+
if let code = AuthErrorCode(rawValue: ns.code) {
197+
XCTAssertEqual(code, .userNotFound)
198+
}
199+
}
200+
}
201+
202+
@available(iOS 15.0, macOS 12.0, tvOS 16.0, *)
203+
func testUnenrollPasskeyFailure() async throws {
204+
let testEmail = "sample.ios.auth@gmail.com"
205+
let testPassword = "sample.ios.auth"
206+
let testCredentialId = "FCBopZ3mzjfPNXqWXXjAM/ZnnlQ="
207+
let auth = Auth.auth()
208+
try await auth.signIn(withEmail: testEmail, password: testPassword)
209+
guard let user = Auth.auth().currentUser else {
210+
XCTFail("Expected a signed-in user")
211+
return
212+
}
213+
try? await user.reload()
214+
do {
215+
let _ = try await user.unenrollPasskey(withCredentialID: testCredentialId)
216+
XCTFail("Expected invalid passkey error")
217+
} catch {
218+
let ns = error as NSError
219+
if let code = AuthErrorCode(rawValue: ns.code) {
220+
XCTAssertEqual(code, .missingPasskeyEnrollment,
221+
"Expected .missingPasskeyEnrollment, got \(code)")
222+
}
223+
let message = (ns.userInfo[NSLocalizedDescriptionKey] as? String ?? "").uppercased()
224+
XCTAssertTrue(
225+
message
226+
.contains(
227+
"CANNOT FIND THE PASSKEY LINKED TO THE CURRENT ACCOUNT"
228+
),
229+
"Expected Missing Passkey Enrollment error, got: \(message)"
230+
)
231+
}
232+
}
233+
}
234+
235+
#endif

0 commit comments

Comments
 (0)