---
title: Yêu cầu xác thực lại khi thực hiện thao tác quan trọng
impact: HIGH
impactDescription: Không yêu cầu re-authentication cho thao tác nhạy cảm cho phép attacker có quyền truy cập vật lý vào device đang unlock thực hiện chuyển tiền, đổi mật khẩu, hoặc xóa account.
tags: swift, ios, re-authentication, biometrics, local-authentication, face-id, touch-id, security
---

## Yêu cầu xác thực lại khi thực hiện thao tác quan trọng

Các thao tác nhạy cảm (chuyển tiền, đổi email/password, xóa account, xem số tài khoản đầy đủ) phải yêu cầu xác thực lại bằng Face ID/Touch ID hoặc PIN ngay cả khi user đã đăng nhập. Đây là yêu cầu bắt buộc của OWASP MASVS và nhiều quy định tài chính.

**Incorrect (không yêu cầu re-auth):**

```swift
import UIKit

class BankingViewController: UIViewController {

    // !! Chuyển tiền không cần xác thực lại - chỉ cần app đang mở
    @IBAction func transferFundsTapped(_ sender: UIButton) {
        let amount = amountField.text ?? "0"
        let recipient = recipientField.text ?? ""
        // Gọi API ngay không cần confirm identity!
        transferFunds(amount: amount, recipient: recipient)
    }

    // !! Xem account number đầy đủ không cần re-auth
    @IBAction func showFullAccountNumber(_ sender: UIButton) {
        accountNumberLabel.text = fullAccountNumber  // Hiển thị ngay
    }
}
```

**Correct (require biometric re-auth):**

```swift
import LocalAuthentication
import UIKit

class ReAuthenticationService {
    enum AuthPurpose {
        case transferFunds(amount: Decimal)
        case changePassword
        case viewSensitiveData(type: String)
        case deleteAccount

        var reason: String {
            switch self {
            case .transferFunds(let amount):
                return "Confirm transfer of \(amount) by authenticating"
            case .changePassword:
                return "Authenticate to change your password"
            case .viewSensitiveData(let type):
                return "Authenticate to view your \(type)"
            case .deleteAccount:
                return "Authenticate to permanently delete your account"
            }
        }
    }

    // SAFE: Require biometric/device passcode re-auth
    func requireAuthentication(for purpose: AuthPurpose) async throws {
        let context = LAContext()
        context.localizedCancelTitle = "Cancel"

        var error: NSError?
        guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {
            throw AuthError.biometricNotAvailable(error?.localizedDescription ?? "")
        }

        try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
            context.evaluatePolicy(
                .deviceOwnerAuthentication,  // Fallback to passcode
                localizedReason: purpose.reason
            ) { success, evalError in
                if success {
                    continuation.resume()
                } else {
                    continuation.resume(throwing: AuthError.authenticationFailed(
                        evalError?.localizedDescription ?? "Authentication failed"
                    ))
                }
            }
        }
    }
}

class BankingViewController: UIViewController {
    private let reAuthService = ReAuthenticationService()

    // SAFE: Require biometric before transfer
    @IBAction func transferFundsTapped(_ sender: UIButton) {
        guard let amount = Decimal(string: amountField.text ?? "0") else { return }
        let recipient = recipientField.text ?? ""

        Task {
            do {
                try await reAuthService.requireAuthentication(for: .transferFunds(amount: amount))
                // Auth passed - thực hiện transfer
                await performTransfer(amount: amount, recipient: recipient)
            } catch AuthError.authenticationFailed {
                await MainActor.run {
                    showAlert("Authentication required to transfer funds.")
                }
            } catch {
                await MainActor.run {
                    showAlert("Authentication unavailable. Please use PIN.")
                }
            }
        }
    }

    // SAFE: Re-auth trước khi hiện số tài khoản
    @IBAction func showFullAccountNumber(_ sender: UIButton) {
        Task {
            do {
                try await reAuthService.requireAuthentication(for: .viewSensitiveData(type: "account number"))
                await MainActor.run {
                    accountNumberLabel.text = fullAccountNumber
                    // Auto-hide sau 30 giây
                    DispatchQueue.main.asyncAfter(deadline: .now() + 30) {
                        self.accountNumberLabel.text = "•••• •••• 1234"
                    }
                }
            } catch {
                // Không hiển thị nếu auth fail
            }
        }
    }
}

enum AuthError: LocalizedError {
    case biometricNotAvailable(String)
    case authenticationFailed(String)

    var errorDescription: String? {
        switch self {
        case .biometricNotAvailable(let msg): return "Biometric not available: \(msg)"
        case .authenticationFailed(let msg): return "Authentication failed: \(msg)"
        }
    }
}
```

**Tools:** LocalAuthentication.framework, OWASP MASVS-AUTH-2, Apple Human Interface Guidelines (biometrics)
