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
    }
}