---
title: Implement lockout sau nhiều lần xác thực thất bại (PIN/password/biometrics)
impact: HIGH
impactDescription: Không giới hạn số lần thử đăng nhập cho phép brute force tấn công PIN 4-6 chữ số trong vài giây. Lockout mechanism và exponential backoff là bắt buộc cho local auth.
tags: swift, ios, brute-force, lockout, pin, local-authentication, security
---

## Implement lockout sau nhiều lần xác thực thất bại (PIN/password/biometrics)

Với local authentication (PIN, passcode), phải implement lockout sau N lần thất bại với exponential backoff, và wipe data hoặc reroute về server auth sau threshold. Server API cũng cần rate limiting cho login attempts.

**Incorrect (không giới hạn số lần thử):**

```swift
import LocalAuthentication

class PINViewController: UIViewController {
    var enteredPIN = ""

    // !! Không giới hạn số lần thử - brute force 0000-9999 trong vài giây
    func validatePIN(_ pin: String) {
        guard let storedPIN = UserDefaults.standard.string(forKey: "user_pin") else { return }
        if pin == storedPIN {
            navigateToHome()
        } else {
            showError("Wrong PIN")
            // Không đếm số lần thất bại!
        }
    }
}
```

**Correct (lockout với exponential backoff):**

```swift
import LocalAuthentication
import Foundation

struct AuthAttemptTracker {
    private let maxAttempts = 5
    private let lockoutKey = "auth_lockout_until"
    private let attemptCountKey = "auth_attempt_count"
    private let keychain = KeychainService.self

    var isLockedOut: Bool {
        guard let lockoutDate = UserDefaults.standard.object(forKey: lockoutKey) as? Date else {
            return false
        }
        return Date() < lockoutDate
    }

    var remainingLockoutSeconds: TimeInterval {
        guard let lockoutDate = UserDefaults.standard.object(forKey: lockoutKey) as? Date else {
            return 0
        }
        return max(0, lockoutDate.timeIntervalSinceNow)
    }

    var failureCount: Int {
        return UserDefaults.standard.integer(forKey: attemptCountKey)
    }

    mutating func recordFailure() {
        let count = failureCount + 1
        UserDefaults.standard.set(count, forKey: attemptCountKey)

        if count >= maxAttempts {
            // Exponential backoff: 30s, 60s, 120s, 240s...
            let lockoutDuration = TimeInterval(30 * pow(2.0, Double(count - maxAttempts)))
            let lockoutUntil = Date().addingTimeInterval(min(lockoutDuration, 3600)) // Max 1h
            UserDefaults.standard.set(lockoutUntil, forKey: lockoutKey)
        }
    }

    func resetAfterSuccess() {
        UserDefaults.standard.removeObject(forKey: attemptCountKey)
        UserDefaults.standard.removeObject(forKey: lockoutKey)
    }
}

class PINViewController: UIViewController {
    private var tracker = AuthAttemptTracker()

    // SAFE: Validate với lockout mechanism
    func validatePIN(_ pin: String) {
        guard !tracker.isLockedOut else {
            let seconds = Int(tracker.remainingLockoutSeconds)
            showError("Too many attempts. Try again in \(seconds)s")
            return
        }

        do {
            let storedPINHash = try KeychainService.readToken(key: "user_pin_hash")
            // Compare hash, không phải plaintext
            let inputHash = hashPIN(pin)
            if inputHash == storedPINHash {
                tracker.resetAfterSuccess()
                navigateToHome()
            } else {
                tracker.recordFailure()
                let remaining = 5 - tracker.failureCount
                if remaining > 0 {
                    showError("Wrong PIN. \(remaining) attempts remaining.")
                } else {
                    showError("Account locked. Use biometrics or contact support.")
                }
            }
        } catch {
            showError("Authentication error")
        }
    }

    private func hashPIN(_ pin: String) -> String {
        // Dùng CryptoKit với salt từ Keychain
        return CryptoPINHasher.hash(pin: pin)
    }
}
```

**Tools:** OWASP MASVS-AUTH-2, OWASP Testing Guide (OTG-AUTHN-003), Instruments (brute force simulation)
