---
name: swift-security
description: Security patterns cho Swift - Keychain, Token management, Auth State Machine, Biometric. Use for secure storage, authentication flows, hoặc khi cần security best practices.
compatibility: "Swift 5.9+, iOS 17+, Security Framework"
metadata:
  author: Velik Ho
  version: "1.0"
  references: "Apple Security Framework, Keychain Services"
---

# Swift Security Patterns

Keychain, Token management, và Auth patterns cho Swift Enterprise.

## When to Use This Skill / Khi Nào Sử Dụng

Use this skill when:
- Storing sensitive data / Lưu trữ dữ liệu nhạy cảm
- Implementing auth flows / Triển khai luồng xác thực
- Token management / Quản lý tokens
- Biometric authentication / Xác thực sinh trắc học

## Core Patterns / Mẫu Chính

### Keychain Helper

```swift
// Services/KeychainHelper.swift

import Security

/// Keychain operations helper
/// Helper cho các thao tác Keychain
enum KeychainHelper {
    
    /// Save value to Keychain
    /// Lưu giá trị vào Keychain
    static func save(key: String, value: String) {
        guard let data = value.data(using: .utf8) else { return }
        
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: AppConstants.keychainService,
            kSecAttrAccount as String: key,
            kSecValueData as String: data
        ]
        
        // Delete existing
        SecItemDelete(query as CFDictionary)
        
        // Add new
        SecItemAdd(query as CFDictionary, nil)
    }
    
    /// Read value from Keychain
    /// Đọc giá trị từ Keychain
    static func read(key: String) -> String? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: AppConstants.keychainService,
            kSecAttrAccount as String: key,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]
        
        var dataTypeRef: AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
        
        guard status == errSecSuccess,
              let data = dataTypeRef as? Data,
              let value = String(data: data, encoding: .utf8)
        else {
            return nil
        }
        
        return value
    }
    
    /// Delete value from Keychain
    /// Xóa giá trị khỏi Keychain
    static func delete(key: String) {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: AppConstants.keychainService,
            kSecAttrAccount as String: key
        ]
        
        SecItemDelete(query as CFDictionary)
    }
}
```

### Auth State Machine

```swift
// Services/AuthState.swift

/// Authentication state enumeration
/// Enum trạng thái xác thực
enum AuthState: Equatable {
    case unknown
    case unauthenticated
    case authenticated(User)
    
    var isAuthenticated: Bool {
        if case .authenticated = self {
            return true
        }
        return false
    }
    
    var user: User? {
        if case .authenticated(let user) = self {
            return user
        }
        return nil
    }
}
```

### Auth Manager

```swift
// Services/AuthManager.swift

/// Authentication manager protocol
protocol AuthManagerProtocol {
    var authState: AuthState { get }
    var accessToken: String? { get }
    func initialize() async
    func login(email: String, password: String) async throws
    func logout()
}

/// Main authentication manager
/// Quản lý xác thực chính
@MainActor
final class AuthManager: ObservableObject, AuthManagerProtocol {
    
    // MARK: - Singleton
    
    static let shared = AuthManager()
    
    // MARK: - Published
    
    @Published private(set) var authState: AuthState = .unknown
    
    // MARK: - Properties
    
    var accessToken: String? {
        KeychainHelper.read(key: StorageKeys.accessToken)
    }
    
    var refreshToken: String? {
        KeychainHelper.read(key: StorageKeys.refreshToken)
    }
    
    // MARK: - Init
    
    private init() {}
    
    // MARK: - Public Methods
    
    /// Initialize auth on app launch
    /// Khởi tạo auth khi app khởi động
    func initialize() async {
        guard accessToken != nil else {
            authState = .unauthenticated
            return
        }
        
        // Try cached user
        if let userData = UserDefaults.standard.data(forKey: StorageKeys.userData),
           let user = try? JSONDecoder().decode(User.self, from: userData) {
            authState = .authenticated(user)
        } else {
            await refreshCurrentUser()
        }
    }
    
    /// Login with credentials
    /// Đăng nhập với credentials
    func login(email: String, password: String) async throws {
        struct LoginRequest: Encodable {
            let email: String
            let password: String
        }
        
        struct LoginResponse: Decodable {
            let accessToken: String
            let refreshToken: String
            let user: User
        }
        
        let request = LoginRequest(email: email, password: password)
        let response: LoginResponse = try await APIService.shared.post(
            endpoint: "/auth/login",
            body: request
        )
        
        // Save tokens
        KeychainHelper.save(key: StorageKeys.accessToken, value: response.accessToken)
        KeychainHelper.save(key: StorageKeys.refreshToken, value: response.refreshToken)
        
        // Cache user
        if let userData = try? JSONEncoder().encode(response.user) {
            UserDefaults.standard.set(userData, forKey: StorageKeys.userData)
        }
        
        authState = .authenticated(response.user)
    }
    
    /// Logout current user
    /// Đăng xuất
    func logout() {
        KeychainHelper.delete(key: StorageKeys.accessToken)
        KeychainHelper.delete(key: StorageKeys.refreshToken)
        UserDefaults.standard.removeObject(forKey: StorageKeys.userData)
        
        authState = .unauthenticated
    }
    
    /// Handle 401 unauthorized
    /// Xử lý 401 unauthorized
    func handleUnauthorized() {
        Task {
            let success = await refreshTokens()
            if !success {
                logout()
            }
        }
    }
    
    // MARK: - Private
    
    private func refreshCurrentUser() async {
        do {
            let user: User = try await APIService.shared.get(endpoint: "/auth/me")
            if let userData = try? JSONEncoder().encode(user) {
                UserDefaults.standard.set(userData, forKey: StorageKeys.userData)
            }
            authState = .authenticated(user)
        } catch {
            authState = .unauthenticated
        }
    }
    
    private func refreshTokens() async -> Bool {
        guard let refreshToken = refreshToken else { return false }
        
        struct RefreshRequest: Encodable { let refreshToken: String }
        struct RefreshResponse: Decodable { let accessToken: String; let refreshToken: String }
        
        do {
            let response: RefreshResponse = try await APIService.shared.post(
                endpoint: "/auth/refresh",
                body: RefreshRequest(refreshToken: refreshToken)
            )
            KeychainHelper.save(key: StorageKeys.accessToken, value: response.accessToken)
            KeychainHelper.save(key: StorageKeys.refreshToken, value: response.refreshToken)
            return true
        } catch {
            return false
        }
    }
}
```

---

## Common Mistakes / Lỗi Thường Gặp

| Mistake | Problem | Solution |
|---------|---------|----------|
| Store tokens in UserDefaults | Insecure | Use Keychain |
| No token refresh | User logged out | Implement refresh flow |
| Missing `@MainActor` | Thread issues | Add to AuthManager |
| Hardcoded service name | Conflicts | Use `AppConstants` |

## Quick Reference / Tham Chiếu Nhanh

| Category | Standard |
|----------|----------|
| Token Storage | Keychain only |
| Auth State | Enum-based state machine |
| Manager | `@MainActor` + `ObservableObject` |
| User Cache | UserDefaults (non-sensitive) |
| 401 Handling | Refresh token, then logout |

## Resources / Tài Nguyên

- [Swift Enterprise Architect](../swift-enterprise-architect/SKILL.md) - Architecture
- [Swift Networking](../swift-networking/SKILL.md) - API layer
- [Apple Keychain Services](https://developer.apple.com/documentation/security/keychain_services)
