Skip to content

Commit 6163b72

Browse files
committed
sample app passkey support
1 parent 8352157 commit 6163b72

File tree

3 files changed

+180
-4
lines changed

3 files changed

+180
-4
lines changed

FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Models/AuthMenu.swift

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ enum AuthMenu: String {
5353
case phoneEnroll
5454
case totpEnroll
5555
case multifactorUnenroll
56+
case passkeySignUp
57+
case passkeyEnroll
58+
case passkeySignIn
59+
case passkeyUnenroll
5660

5761
// More intuitively named getter for `rawValue`.
5862
var id: String { rawValue }
@@ -139,6 +143,15 @@ enum AuthMenu: String {
139143
return "TOTP Enroll"
140144
case .multifactorUnenroll:
141145
return "Multifactor unenroll"
146+
// Passkey
147+
case .passkeySignUp:
148+
return "Sign Up with Passkey"
149+
case .passkeyEnroll:
150+
return "Enroll with Passkey"
151+
case .passkeySignIn:
152+
return "Sign In with Passkey"
153+
case .passkeyUnenroll:
154+
return "Unenroll Passkey"
142155
}
143156
}
144157

@@ -220,6 +233,14 @@ enum AuthMenu: String {
220233
self = .totpEnroll
221234
case "Multifactor unenroll":
222235
self = .multifactorUnenroll
236+
case "Sign Up with Passkey":
237+
self = .passkeySignUp
238+
case "Enroll with Passkey":
239+
self = .passkeyEnroll
240+
case "Sign In with Passkey":
241+
self = .passkeySignIn
242+
case "Unenroll Passkey":
243+
self = .passkeyUnenroll
223244
default:
224245
return nil
225246
}
@@ -354,9 +375,20 @@ class AuthMenuData: DataSourceProvidable {
354375
return Section(headerDescription: header, items: items)
355376
}
356377

378+
static var passkeySection: Section {
379+
let header = "Passkey"
380+
let items: [Item] = [
381+
Item(title: AuthMenu.passkeySignUp.name),
382+
Item(title: AuthMenu.passkeyEnroll.name),
383+
Item(title: AuthMenu.passkeySignIn.name),
384+
Item(title: AuthMenu.passkeyUnenroll.name),
385+
]
386+
return Section(headerDescription: header, items: items)
387+
}
388+
357389
static let sections: [Section] =
358390
[settingsSection, providerSection, emailPasswordSection, otherSection, recaptchaSection,
359-
customAuthDomainSection, appSection, oobSection, multifactorSection]
391+
customAuthDomainSection, appSection, oobSection, multifactorSection, passkeySection]
360392

361393
static var authLinkSections: [Section] {
362394
let allItems = [providerSection, emailPasswordSection, otherSection].flatMap { $0.items }

FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Utility/Extensions.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ extension User: DataSourceProvidable {
3333
return Section(headerDescription: "Info", items: items)
3434
}
3535

36+
private var passkeysSection: Section {
37+
let passkeys = enrolledPasskeys ?? []
38+
guard !passkeys.isEmpty else {
39+
return Section(
40+
headerDescription: "Passkeys",
41+
items: [Item(title: "None", detailTitle: "No passkeys enrolled")]
42+
)
43+
}
44+
let items: [Item] = passkeys.map { info in
45+
Item(title: info.name, detailTitle: info.credentialID)
46+
}
47+
return Section(headerDescription: "Passkeys", items: items)
48+
}
49+
3650
private var metaDataSection: Section {
3751
let metadataRows = [
3852
Item(title: metadata.lastSignInDate?.description, detailTitle: "Last Sign-in Date"),
@@ -62,7 +76,7 @@ extension User: DataSourceProvidable {
6276
}
6377

6478
var sections: [Section] {
65-
[infoSection, metaDataSection, otherSection, actionSection]
79+
[infoSection, passkeysSection, metaDataSection, otherSection, actionSection]
6680
}
6781
}
6882

FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AuthViewController.swift

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,18 @@ class AuthViewController: UIViewController, DataSourceProviderDelegate {
191191

192192
case .multifactorUnenroll:
193193
mfaUnenroll()
194+
195+
case .passkeySignUp:
196+
passkeySignUp()
197+
198+
case .passkeyEnroll:
199+
Task { await passkeyEnroll() }
200+
201+
case .passkeySignIn:
202+
Task { await passkeySignIn() }
203+
204+
case .passkeyUnenroll:
205+
Task { await passkeyUnenroll() }
194206
}
195207
}
196208

@@ -922,6 +934,87 @@ class AuthViewController: UIViewController, DataSourceProviderDelegate {
922934
}
923935
}
924936

937+
// MARK: - Passkey
938+
939+
private func passkeySignUp() {
940+
guard #available(iOS 16.0, macOS 12.0, tvOS 16.0, *) else {
941+
print("OS version is not supported for this action.")
942+
return
943+
}
944+
Task {
945+
do {
946+
_ = try await AppManager.shared.auth().signInAnonymously()
947+
print("sign-in anonymously succeeded.")
948+
if let uid = AppManager.shared.auth().currentUser?.uid {
949+
print("User ID: \(uid)")
950+
}
951+
// Continue to enroll a passkey.
952+
await passkeyEnroll()
953+
} catch {
954+
print("sign-in anonymously failed: \(error.localizedDescription)")
955+
self.showAlert(for: "Anonymous Sign-In Failed")
956+
}
957+
}
958+
}
959+
960+
private func passkeyEnroll() async {
961+
guard let user = AppManager.shared.auth().currentUser else {
962+
showAlert(for: "Please sign in first.")
963+
return
964+
}
965+
let passkeyName = await showTextInputPrompt(with: "Passkey name")
966+
guard #available(iOS 16.0, macOS 12.0, tvOS 16.0, *) else {
967+
showAlert(for: "Not Supported", message: "This OS version does not support passkeys.")
968+
return
969+
}
970+
971+
do {
972+
let request = try await user.startPasskeyEnrollment(withName: passkeyName)
973+
let controller = ASAuthorizationController(authorizationRequests: [request])
974+
controller.delegate = self
975+
controller.presentationContextProvider = self
976+
controller.performRequests()
977+
print("Started passkey enrollment (challenge created).")
978+
} catch {
979+
showAlert(for: "Passkey enrollment failed", message: error.localizedDescription)
980+
print("startPasskeyEnrollment failed: \(error.localizedDescription)")
981+
}
982+
}
983+
984+
private func passkeySignIn() async {
985+
guard #available(iOS 16.0, macOS 12.0, tvOS 16.0, *) else {
986+
print("OS version is not supported for this action.")
987+
return
988+
}
989+
do {
990+
let request = try await AppManager.shared.auth().startPasskeySignIn()
991+
let controller = ASAuthorizationController(authorizationRequests: [request])
992+
controller.delegate = self
993+
controller.presentationContextProvider = self
994+
controller.performRequests()
995+
print("Started passkey sign in (challenge created).")
996+
} catch {
997+
print("Passkey sign-in failed with error: \(error)")
998+
}
999+
}
1000+
1001+
private func passkeyUnenroll() async {
1002+
guard let user = AppManager.shared.auth().currentUser else {
1003+
showAlert(for: "Please sign in first.")
1004+
return
1005+
}
1006+
guard let credentialId = await showTextInputPrompt(with: "Credential Id") else {
1007+
print("Passkey unenrollment cancelled: no credential id entered.")
1008+
return
1009+
}
1010+
do {
1011+
let _ = try await user.unenrollPasskey(withCredentialID: credentialId)
1012+
} catch {
1013+
showAlert(for: "Passkey unenrollment failed", message: error.localizedDescription)
1014+
print("unenrollPasskey failed: \(error.localizedDescription)")
1015+
}
1016+
}
1017+
9251018
// MARK: - Private Helpers
9261019

9271020
private func showTextInputPrompt(with message: String, completion: ((String) -> Void)? = nil) {
@@ -1027,6 +1120,43 @@ extension AuthViewController: ASAuthorizationControllerDelegate,
10271120

10281121
func authorizationController(controller: ASAuthorizationController,
10291122
didCompleteWithAuthorization authorization: ASAuthorization) {
1123+
if #available(iOS 16.0, macOS 12.0, tvOS 16.0, *),
1124+
let regCred = authorization.credential
1125+
as? ASAuthorizationPlatformPublicKeyCredentialRegistration {
1126+
Task { @MainActor [weak self] in
1127+
guard let self else { return }
1128+
do {
1129+
guard let user = AppManager.shared.auth().currentUser else {
1130+
self.showAlert(for: "Finalize failed", message: "No signed-in user.")
1131+
return
1132+
}
1133+
_ = try await user.finalizePasskeyEnrollment(withPlatformCredential: regCred)
1134+
self.showAlert(for: "Passkey Enrollment", message: "Succeeded")
1135+
print("Passkey Enrollment succeeded.")
1136+
} catch {
1137+
self.showAlert(for: "Passkey Enrollment failed", message: error.localizedDescription)
1138+
print("Finalize enrollment failed: \(error.localizedDescription)")
1139+
}
1140+
}
1141+
return
1142+
}
1143+
if let assertion = authorization
1144+
.credential as? ASAuthorizationPlatformPublicKeyCredentialAssertion {
1145+
Task { @MainActor [weak self] in
1146+
guard let self else { return }
1147+
do {
1148+
let _ = try await AppManager.shared.auth()
1149+
.finalizePasskeySignIn(withPlatformCredential: assertion)
1150+
self.showAlert(for: "Passkey Sign-In", message: "Succeeded")
1151+
print("Passkey sign-in succeeded.")
1152+
self.transitionToUserViewController()
1153+
} catch {
1154+
self.showAlert(for: "Passkey Sign-In failed", message: error.localizedDescription)
1155+
print("Finalize passkey sign-in failed: \(error.localizedDescription)")
1156+
}
1157+
}
1158+
return
1159+
}
10301160
guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential
10311161
else {
10321162
print("Unable to retrieve AppleIDCredential")
@@ -1074,10 +1204,10 @@ extension AuthViewController: ASAuthorizationControllerDelegate,
10741204

10751205
func authorizationController(controller: ASAuthorizationController,
10761206
didCompleteWithError error: any Error) {
1077-
// Ensure that you have:
1207+
print("Apple authorization failed: \(error)")
1208+
// for Sign In with Apple, ensure that you have:
10781209
// - enabled `Sign in with Apple` on the Firebase console
10791210
// - added the `Sign in with Apple` capability for this project
1080-
print("Sign in with Apple failed: \(error)")
10811211
}
10821212

10831213
// MARK: ASAuthorizationControllerPresentationContextProviding

0 commit comments

Comments
 (0)