Modern Authentication Methods

Modern Authentication Methods

Mobile platforms offer various authentication methods, each with unique security characteristics and user experience implications.

iOS Biometric Authentication Implementation:

// iOS - Comprehensive biometric authentication system
import LocalAuthentication
import CryptoKit

class BiometricAuthManager {
    
    enum BiometricType {
        case none
        case touchID
        case faceID
        case opticID // visionOS
    }
    
    enum AuthError: Error {
        case biometryNotAvailable
        case biometryNotEnrolled
        case userCancel
        case userFallback
        case biometryLockout
        case invalidCredentials
        case keychainError
    }
    
    private let context = LAContext()
    private let keychain = KeychainWrapper()
    
    // Check biometric availability and type
    var availableBiometricType: BiometricType {
        var error: NSError?
        
        guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
            return .none
        }
        
        switch context.biometryType {
        case .faceID:
            return .faceID
        case .touchID:
            return .touchID
        case .opticID:
            return .opticID
        default:
            return .none
        }
    }
    
    // Authenticate with biometrics and store secure session
    func authenticateUser(
        reason: String,
        fallbackTitle: String? = "Use Passcode",
        completion: @escaping (Result<SecureSession, AuthError>) -> Void
    ) {
        // Configure context
        context.localizedFallbackTitle = fallbackTitle
        context.localizedCancelTitle = "Cancel"
        
        // Set authentication validity duration
        context.touchIDAuthenticationAllowableReuseDuration = 30 // 30 seconds
        
        // Invalidate context on app background
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(invalidateContext),
            name: UIApplication.didEnterBackgroundNotification,
            object: nil
        )
        
        // Perform biometric authentication
        context.evaluatePolicy(
            .deviceOwnerAuthenticationWithBiometrics,
            localizedReason: reason
        ) { [weak self] success, error in
            DispatchQueue.main.async {
                if success {
                    // Generate secure session after successful authentication
                    if let session = self?.createSecureSession() {
                        completion(.success(session))
                    } else {
                        completion(.failure(.keychainError))
                    }
                } else {
                    // Handle specific error cases
                    completion(.failure(self?.handleBiometricError(error) ?? .invalidCredentials))
                }
            }
        }
    }
    
    // Create cryptographically secure session
    private func createSecureSession() -> SecureSession? {
        // Generate session token
        let sessionToken = generateSecureToken()
        
        // Create session with expiration
        let session = SecureSession(
            token: sessionToken,
            createdAt: Date(),
            expiresAt: Date().addingTimeInterval(3600), // 1 hour
            biometricType: availableBiometricType
        )
        
        // Store in keychain with biometric protection
        do {
            let sessionData = try JSONEncoder().encode(session)
            
            // Create access control requiring biometric authentication
            var error: Unmanaged<CFError>?
            guard let accessControl = SecAccessControlCreateWithFlags(
                kCFAllocatorDefault,
                kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
                [.biometryCurrentSet, .privateKeyUsage],
                &error
            ) else {
                return nil
            }
            
            // Save to keychain
            let query: [String: Any] = [
                kSecClass as String: kSecClassGenericPassword,
                kSecAttrService as String: "com.app.session",
                kSecAttrAccount as String: "current_session",
                kSecValueData as String: sessionData,
                kSecAttrAccessControl as String: accessControl
            ]
            
            SecItemDelete(query as CFDictionary) // Remove old session
            let status = SecItemAdd(query as CFDictionary, nil)
            
            return status == errSecSuccess ? session : nil
        } catch {
            return nil
        }
    }
    
    private func generateSecureToken() -> String {
        let tokenData = Data((0..<32).map { _ in UInt8.random(in: 0...255) })
        return tokenData.base64EncodedString()
    }
    
    @objc private func invalidateContext() {
        context.invalidate()
    }
}

struct SecureSession: Codable {
    let token: String
    let createdAt: Date
    let expiresAt: Date
    let biometricType: BiometricAuthManager.BiometricType
    
    var isValid: Bool {
        return Date() < expiresAt
    }
}

Android Biometric Authentication:

// Android - Modern biometric authentication implementation
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import javax.crypto.Cipher
import javax.crypto.SecretKey

class BiometricAuthManager(private val activity: FragmentActivity) {
    
    private lateinit var biometricPrompt: BiometricPrompt
    private lateinit var promptInfo: BiometricPrompt.PromptInfo
    
    companion object {
        private const val KEY_NAME = "BiometricKey"
        private const val ANDROID_KEYSTORE = "AndroidKeyStore"
        private const val ENCRYPTION_BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM
        private const val ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_NONE
        private const val ENCRYPTION_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
    }
    
    // Check biometric availability
    fun canAuthenticate(): BiometricStatus {
        val biometricManager = BiometricManager.from(activity)
        
        return when (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)) {
            BiometricManager.BIOMETRIC_SUCCESS -> BiometricStatus.AVAILABLE
            BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> BiometricStatus.NO_HARDWARE
            BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> BiometricStatus.UNAVAILABLE
            BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> BiometricStatus.NOT_ENROLLED
            else -> BiometricStatus.UNKNOWN
        }
    }
    
    // Authenticate and create secure session
    fun authenticate(
        title: String,
        subtitle: String? = null,
        description: String? = null,
        onSuccess: (SecureSession) -> Unit,
        onError: (AuthError) -> Unit
    ) {
        val executor = ContextCompat.getMainExecutor(activity)
        
        biometricPrompt = BiometricPrompt(
            activity,
            executor,
            object : BiometricPrompt.AuthenticationCallback() {
                
                override fun onAuthenticationSucceeded(
                    result: BiometricPrompt.AuthenticationResult
                ) {
                    super.onAuthenticationSucceeded(result)
                    
                    // Use crypto object to ensure hardware-backed authentication
                    result.cryptoObject?.let { cryptoObject ->
                        val session = createSecureSession(cryptoObject)
                        onSuccess(session)
                    } ?: run {
                        // Fallback for devices without crypto support
                        val session = createBasicSession()
                        onSuccess(session)
                    }
                }
                
                override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                    super.onAuthenticationError(errorCode, errString)
                    
                    val error = when (errorCode) {
                        BiometricPrompt.ERROR_USER_CANCELED -> AuthError.USER_CANCELED
                        BiometricPrompt.ERROR_LOCKOUT -> AuthError.LOCKOUT
                        BiometricPrompt.ERROR_LOCKOUT_PERMANENT -> AuthError.LOCKOUT_PERMANENT
                        BiometricPrompt.ERROR_NO_BIOMETRICS -> AuthError.NOT_ENROLLED
                        else -> AuthError.UNKNOWN
                    }
                    
                    onError(error)
                }
                
                override fun onAuthenticationFailed() {
                    super.onAuthenticationFailed()
                    // Don't close prompt, allow retry
                }
            }
        )
        
        // Configure prompt
        promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle(title)
            .apply {
                subtitle?.let { setSubtitle(it) }
                description?.let { setDescription(it) }
            }
            .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
            .setNegativeButtonText("Cancel")
            .build()
        
        // Try to use crypto-based authentication
        try {
            val cipher = getCipher()
            val secretKey = getOrCreateSecretKey()
            cipher.init(Cipher.ENCRYPT_MODE, secretKey)
            
            biometricPrompt.authenticate(
                promptInfo,
                BiometricPrompt.CryptoObject(cipher)
            )
        } catch (e: Exception) {
            // Fallback to non-crypto authentication
            biometricPrompt.authenticate(promptInfo)
        }
    }
    
    private fun createSecureSession(cryptoObject: BiometricPrompt.CryptoObject): SecureSession {
        val cipher = cryptoObject.cipher!!
        
        // Encrypt session data
        val sessionToken = generateSecureToken()
        val encryptedToken = cipher.doFinal(sessionToken.toByteArray())
        
        val session = SecureSession(
            token = Base64.encodeToString(encryptedToken, Base64.NO_WRAP),
            iv = Base64.encodeToString(cipher.iv, Base64.NO_WRAP),
            createdAt = System.currentTimeMillis(),
            expiresAt = System.currentTimeMillis() + (60 * 60 * 1000), // 1 hour
            isHardwareBacked = true
        )
        
        // Store in encrypted SharedPreferences
        SecurePreferencesManager(activity).saveSession(session)
        
        return session
    }
    
    private fun getOrCreateSecretKey(): SecretKey {
        val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
        keyStore.load(null)
        
        return if (keyStore.containsAlias(KEY_NAME)) {
            keyStore.getKey(KEY_NAME, null) as SecretKey
        } else {
            createSecretKey()
        }
    }
    
    private fun createSecretKey(): SecretKey {
        val keyGenerator = KeyGenerator.getInstance(ENCRYPTION_ALGORITHM, ANDROID_KEYSTORE)
        
        val keyGenParameterSpec = KeyGenParameterSpec.Builder(
            KEY_NAME,
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
        )
            .setBlockModes(ENCRYPTION_BLOCK_MODE)
            .setEncryptionPaddings(ENCRYPTION_PADDING)
            .setKeySize(256)
            .setUserAuthenticationRequired(true)
            .setUserAuthenticationValidityDurationSeconds(300) // 5 minutes
            .setInvalidatedByBiometricEnrollment(true)
            .build()
        
        keyGenerator.init(keyGenParameterSpec)
        return keyGenerator.generateKey()
    }
    
    enum class BiometricStatus {
        AVAILABLE,
        NO_HARDWARE,
        UNAVAILABLE,
        NOT_ENROLLED,
        UNKNOWN
    }
    
    enum class AuthError {
        USER_CANCELED,
        LOCKOUT,
        LOCKOUT_PERMANENT,
        NOT_ENROLLED,
        UNKNOWN
    }
}

data class SecureSession(
    val token: String,
    val iv: String,
    val createdAt: Long,
    val expiresAt: Long,
    val isHardwareBacked: Boolean
) {
    fun isValid(): Boolean = System.currentTimeMillis() < expiresAt
}