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
}