Section 5/161 menit
5. ASAuthorizationController: Advanced Usage
5. ASAuthorizationController: Advanced Usage
Lifecycle yang Tepat
swift
import AuthenticationServices
@MainActor
class AuthenticationCoordinator: NSObject {
// Pertahankan referensi — controller yang di-deallocate akan cancel request
private var authController: ASAuthorizationController?
private var continuation: CheckedContinuation<ASAuthorization, Error>?
// Registration
func registerPasskey(
username: String,
userID: Data,
challenge: Data,
rpID: String
) async throws -> ASAuthorizationPlatformPublicKeyCredentialRegistration {
let authorization = try await performRequest {
let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier: rpID
)
let request = provider.createCredentialRegistrationRequest(
challenge: challenge,
name: username,
userID: userID
)
request.userVerificationPreference = .required // Minta biometrik
request.attestationPreference = .direct // Minta attestation
return request
}
guard let registration = authorization.credential
as? ASAuthorizationPlatformPublicKeyCredentialRegistration else {
throw PasskeyError.unexpectedCredentialType
}
return registration
}
// Assertion
func signInWithPasskey(
challenge: Data,
rpID: String,
allowedCredentials: [Data] = []
) async throws -> ASAuthorizationPlatformPublicKeyCredentialAssertion {
let authorization = try await performRequest {
let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier: rpID
)
let request = provider.createCredentialAssertionRequest(challenge: challenge)
if !allowedCredentials.isEmpty {
request.allowedCredentials = allowedCredentials.map {
ASAuthorizationPlatformPublicKeyCredentialDescriptor(credentialID: $0)
}
}
return request
}
guard let assertion = authorization.credential
as? ASAuthorizationPlatformPublicKeyCredentialAssertion else {
throw PasskeyError.unexpectedCredentialType
}
return assertion
}
// Generic helper menggunakan continuation
private func performRequest(
makeRequest: () -> ASAuthorizationRequest
) async throws -> ASAuthorization {
return try await withCheckedThrowingContinuation { continuation in
self.continuation = continuation
let request = makeRequest()
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
self.authController = controller // Retain!
}
}
}
extension AuthenticationCoordinator: ASAuthorizationControllerDelegate {
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization
) {
continuation?.resume(returning: authorization)
continuation = nil
authController = nil
}
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithError error: Error
) {
continuation?.resume(throwing: error)
continuation = nil
authController = nil
}
}
extension AuthenticationCoordinator: ASAuthorizationControllerPresentationContextProviding {
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
// Kembalikan window yang aktif
guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = scene.windows.first(where: { $0.isKeyWindow }) else {
return UIWindow()
}
return window
}
}
Kombinasi Passkey + Password dalam Satu Request
swift
// Tampilkan semua credential yang tersedia — passkey DAN password tersimpan
func signInWithAnyMethod(challenge: Data, rpID: String) async throws -> ASAuthorization {
let passkeyProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier: rpID
)
let passkeyRequest = passkeyProvider.createCredentialAssertionRequest(challenge: challenge)
let passwordProvider = ASAuthorizationPasswordProvider()
let passwordRequest = passwordProvider.createRequest()
return try await withCheckedThrowingContinuation { continuation in
// Controller dengan KEDUA request — system UI memilih yang terbaik
let controller = ASAuthorizationController(authorizationRequests: [
passkeyRequest,
passwordRequest
])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
self.authController = controller
self.continuation = continuation
}
}
// Handle hasil — bisa credential apapun
func handleSignIn(authorization: ASAuthorization) async throws {
switch authorization.credential {
case let passkeyAssertion as ASAuthorizationPlatformPublicKeyCredentialAssertion:
try await verifyPasskeyAssertion(passkeyAssertion)
case let passwordCredential as ASPasswordCredential:
try await signInWithPassword(passwordCredential.user, passwordCredential.password)
default:
throw AuthError.unknownCredentialType
}
}