인터페이스는 현지화되어 있으며, Skill 소개와 설명 문서는 선택한 언어로 AI가 자동 번역합니다. 코드·명령어·경로·전문 용어는 원문을 유지하며 원작자 문서가 우선합니다.
Skills 둘러보기
개발 및 엔지니어링 · SKILL.md

mobile-biometrics

mobile-biometrics

Use this skill when the user says 'biometrics', 'Face ID', 'Touch ID', 'fingerprint', 'fingerprint auth', 'biometric auth', 'face unlock', 'local authentication', 'biometric prompt', 'device credential'. Implement biometric authentication with secure storage on iOS and Android. Do NOT use for: server-side authentication or password-based auth.

Independent GitHub snapshot · Automated classification · Original documentation governs

Skill 문서

GitHub

name: mobile-biometrics description: > Use this skill when the user says 'biometrics', 'Face ID', 'Touch ID', 'fingerprint', 'fingerprint auth', 'biometric auth', 'face unlock', 'local authentication', 'biometric prompt', 'device credential'. Implement biometric authentication with secure storage on iOS and Android. Do NOT use for: server-side authentication or password-based auth. compatibility: claude-code: true cursor: true codex: true windsurf: true tags: [mobile, biometrics, auth, phase-7, universal] version: "2.0.0" author: "j4flmao" license: "MIT"

Mobile Biometrics

Purpose

Guide for implementing biometric authentication (Face ID, Touch ID, fingerprint) with secure storage and fallback UX.

Agent Protocol

Trigger

Phrases: "biometrics", "Face ID", "Touch ID", "fingerprint", "fingerprint auth", "biometric auth", "face unlock", "local authentication", "biometric prompt", "device credential"

Input Context

  • Target platforms (iOS, Android, or cross-platform)
  • Sensitivity level of protected operations
  • Existing auth system (if any)
  • UX requirements for lockout and fallback

Output Artifact

Biometric auth module: availability check, prompt flow, secure storage with biometric protection, fallback to device credential, UX handling.

Response Format

No preamble. No postamble. No explanations. No filler/hedging/transitions. Compress output — why use many token when few do trick.

Completion Criteria

  • Biometric prompt appears and resolves correctly
  • Data encrypted with biometric key — inaccessible without auth
  • Device credential fallback works when biometrics unavailable
  • User can opt in/out of biometric auth
  • 5-failure lockout enforced

Max Response Length

6000 tokens

Architecture / Decision Trees

Biometric Use Case Decision Tree

What operation are you protecting?
├── App unlock (every launch) → Biometric + device credential
│   iOS: LAContext(.deviceOwnerAuthentication)
│   Android: BiometricPrompt with DEVICE_CREDENTIAL
├── High-sensitivity (payments, passwords, personal data)
│   → Class 3 (Strong) biometric required
│   Fallback to device credential
├── Low-sensitivity (dashboard view, quick action)
│   → Class 2 (Weak) biometric acceptable
│   No fallback needed — just require re-auth
└── Data decryption (read stored secrets)
    → Biometric key (Android: setUserAuthenticationRequired, iOS: biometryCurrentSet)
    Key invalidated on biometric enrollment change

Biometric Class Selection

Which Android biometric class?
├── Class 3 (Strong) → BIOMETRIC_STRONG
│   Pin/pattern + hardware-backed biometric
│   Used for: payments, auth tokens, PII
├── Class 2 (Weak) → BIOMETRIC_WEAK
│   Face recognition, iris — less secure
│   Used for: convenience unlock, non-sensitive
└── Device credential → DEVICE_CREDENTIAL
    PIN, pattern, password — always available

Workflow

  1. Biometric type detection — Three categories of biometric authentication with varying security levels. Class 3 (Strong): Face ID (iPhone X+), Touch ID (iPhone 5s+), Pixel Imprint, ultrasonic fingerprint. Class 2 (Weak): face unlock on budget Android devices, iris scanner. Class 1: convenience face unlock. Android: Check with BiometricManager.canAuthenticate(BIOMETRIC_STRONG) vs BIOMETRIC_WEAK. iOS: LAContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics) — Face ID on devices with TrueDepth camera, Touch ID on devices with Touch ID sensor. Device credential (PIN, pattern, password) is always available as fallback and is considered a biometric alternative on iOS (deviceOwnerAuthentication), but not equivalent to biometric on Android (DEVICE_CREDENTIAL flag).

  2. Biometric availability check — Before showing a biometric prompt, check: (a) Is biometric hardware available? (hardware present). (b) Is biometric enrolled? (user has registered at least one fingerprint/face). (c) Is device credential configured? (PIN/password set — required fallback). (d) Are there any transient issues? (security update required, sensor dirty, too many attempts — lockout). Handle each failure case with a specific user-facing message. On Android, BiometricManager returns distinct error codes. On iOS, LAContext.canEvaluatePolicy returns NSError with LAError codes. Never show biometric prompt without first checking availability.

  3. Authentication prompt flow — Flow: user triggers protected action -> check availability -> if biometric available -> show biometric prompt with reason string -> user authenticates -> on success -> grant access -> on failure -> allow retry (up to 5 attempts) -> on 5th failure -> lockout -> force device credential. If biometric not available at start -> fall through to device credential. The reason string is mandatory on both platforms and displayed prominently. On iOS, localizedReason is shown in the Face ID dialog. On Android, setTitle and setSubtitle are shown in the system prompt. After successful authentication, the app receives a callback with (optionally) a CryptoObject for decryption.

  4. Secure storage with biometric protection — Store sensitive data (auth tokens, encryption keys, passwords) in platform secure stores with biometric access control. Android: EncryptedSharedPreferences backed by Android Keystore — set setUserAuthenticationRequired(true) on the master key to require biometric before reading. Or use Keystore SecretKey with setUserAuthenticationRequired(true) and encrypt/decrypt data through CryptoObject. setInvalidatedByBiometricEnrollment(true) ensures key is destroyed if new biometric is enrolled (prevents stolen biometric from accessing old data). iOS: Keychain with SecAccessControlCreateWithFlags using biometryCurrentSet — item is only accessible after biometric authentication and is invalidated on biometric enrollment change. For "cache after auth" pattern, use setUserAuthenticationValidityDurationSeconds (Android) or kSecUseAuthenticationUIFallback (iOS).

  5. Fallback and UX design — Always provide device credential (PIN/password) as fallback — never block users out. Three-tier fallback chain: Biometric -> Device Credential -> App Password (optional, for users who can't use either). UX patterns: opt-in onboarding screen explaining what biometrics protect, toggle in Settings to enable/disable, clear description of protected operations, graceful degradation on devices without biometric hardware. Lockout: after 5 consecutive biometric failures, biometric is blocked (iOS indefinitely until device credential used, Android for 30 seconds escalating). After lockout, show device credential prompt automatically. Never let users get stuck — always offer the next fallback option.

  6. Cross-platform implementation — Using Capacitor Biometric plugin (@capacitor/biometric): single API for both platforms, handles availability check, prompt, and fallback. Using Expo LocalAuthentication: similar cross-platform API with hasHardwareAsync(), isEnrolledAsync(), authenticateAsync(). For React Native: react-native-biometrics or react-native-keychain. Cross-platform wrappers simplify code but may not expose all platform-specific features (CryptoObject, fine-grained error handling, key invalidation control). For sensitive apps, use platform-native implementation with cross-platform coordination.

Biometric Strength Comparison

LevelAndroidiOSSecurity
Strong (Class 3)Fingerprint, Face (Class 3)Face ID, Touch IDHigh
Weak (Class 2)Face unlock, IrisFace ID with attention not requiredMedium
Convenience (Class 1)Basic face detectionNoneLow
Device CredentialPIN, Pattern, PasswordPIN, PasswordVaries

Best Practices

  • Never store raw biometric data — only authentication results
  • Biometric data never leaves the device — no server-side biometric matching
  • Device credential fallback is mandatory — no biometric-only gates for critical features
  • User must explicitly opt in — no silent biometric enrollment
  • Protect high-sensitivity operations: payments, password viewing, profile changes, security settings
  • Cache biometric auth result for session duration (not per-action)

Common Pitfalls

  • Lockout without recovery: If no device credential fallback, user is permanently locked out. Always provide it.
  • Camera-based face unlock fails in low light: Advise user or fallback to device credential.
  • Biometric changed alert ignored: Apps that don't monitor onAuthenticationError with ERROR_USER_CANCELED after biometric change leave old data accessible.
  • Key invalidation surprise: Keys invalidated on enrollment change delete encrypted data. Migrate before invalidation.
  • Missing usage description strings: iOS rejects apps without NSFaceIDUsageDescription in Info.plist.
  • Calling biometric on main thread: Android BiometricPrompt UI must be on main thread; heavy post-auth work goes to background thread.

Security Considerations

  • Biometric match probability: Face ID 1:1,000,000; Touch ID 1:50,000 — not cryptographic certainty
  • Server-side auth must NOT rely solely on biometric claim — use biometric for local key release, then authenticate with key
  • Anti-spoofing: Face ID with attention awareness is better than basic face unlock
  • iOS localizedReason displayed in dialog — make it clear to user what's being authenticated
  • Android setConfirmationRequired(true) adds button press to fingerprint — prevents accidental auth

Configuration Reference

// Android — BiometricPrompt config
val promptInfo = BiometricPrompt.PromptInfo.Builder()
    .setTitle("Verify identity")
    .setSubtitle("Access your secure vault")
    .setAllowedAuthenticators(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
    .setConfirmationRequired(false)
    .build()
// iOS — LAContext
let context = LAContext()
context.localizedReason = "Unlock your premium features"
context.localizedFallbackTitle = "Enter Passcode"
context.touchIDAuthenticationAllowableReuseDuration = 10 // seconds cached

Biometric Key Management & Crypto Integration

Biometric authentication is most valuable when used to protect cryptographic keys, not just as a boolean gate. The pattern: (1) generate a strong symmetric key (AES-256) in platform secure hardware, (2) bind the key to biometric authentication using setUserAuthenticationRequired(true), (3) use this key to encrypt/decrypt sensitive data (auth tokens, API keys, user credentials), (4) the key is only released when biometric authentication succeeds. Android Keystore provides this via KeyGenParameterSpec with setUserAuthenticationRequired(true) and setInvalidatedByBiometricEnrollment(true). iOS provides this via Keychain with SecAccessControlCreateWithFlags and biometryCurrentSet. For cross-platform, use platform-specific key storage and expose through a uniform SecureEnclave interface. Key invalidation on biometric enrollment change is critical security behavior — when user adds a new fingerprint/face, existing biometric keys are destroyed, requiring re-encryption of stored data. Apps must handle KeyPermanentlyInvalidatedException (Android) or errSecItemNotFound (iOS) gracefully by re-prompting for fresh credentials and re-encrypting.

Multi-Factor Authentication Architecture

Biometrics alone should not grant access to high-value operations. For payments, password changes, or PII viewing, combine biometrics with another factor (MFA). Decision tree:

Operation sensitivity level?
├── Low (view dashboard, app unlock) → Biometric only
├── Medium (view profile, read messages) → Biometric + device credential
├── High (payments, password change, delete account)
│   ├── Option A: Biometric + OTP (TOTP or SMS)
│   └── Option B: Biometric + server-issued challenge
└── Critical (large transfers, admin actions)
    → Biometric + OTP + device credential (3-factor)

The MFA flow: (1) user authenticates with biometric on device, (2) app sends proof of biometric auth (key attestation or signature) to server, (3) server verifies the attestation and issues an OTP challenge, (4) user enters OTP from authenticator app, (5) server validates and grants access. For offline MFA, use time-based OTP generated from a seed stored in the biometric-protected keystore.

Biometric Error Handling Decision Tree

Biometric authentication result?
├── Success → Grant access, cache auth for session
├── Failure (auth failed, fingerprint not recognized)
│   ├── Attempts < 5 → Show "Try again" with haptic feedback
│   └── Attempts >= 5 → Lockout biometric, force device credential
├── Error: biometric not enrolled → Redirect to Settings → enroll
├── Error: hardware unavailable → Fall through to device credential
├── Error: user canceled → Wait for next user action (don't immediately re-prompt)
├── Error: security update required → Show system update prompt
├── Error: sensor dirty/wet (Android specific) → "Clean sensor" message
└── Error: biometric changed (key invalidation) → Re-encrypt data with new biometric key

Production Considerations

Biometric Failure Modes

FailureSymptomMitigation
No biometric enrolledFeature disabledGuide user to Settings to enroll
Key invalidation on biometric changeDecryption fails on existing dataCatch KeyPermanentlyInvalidatedException, re-encrypt
Lockout (5+ failures)Biometric unavailableForce device credential, never show biometric UI
iOS Face ID not configuredcanEvaluatePolicy returns falseInfo.plist missing NSFaceIDUsageDescription
Android sensor dirty"Try again" repeatedlyGuide user to clean sensor
Biometric hardware absentBIOMETRIC_ERROR_HW_UNAVAILABLEDisable biometric section, fallback to PIN

Troubleshooting Checklist

  • Verify NSFaceIDUsageDescription in Info.plist (iOS)
  • Check BiometricManager.canAuthenticate(BIOMETRIC_STRONG) before showing prompt (Android)
  • Confirm setAllowedAuthenticators() includes DEVICE_CREDENTIAL fallback
  • Validate that biometric key uses setInvalidatedByBiometricEnrollment(true)
  • Test lockout flow: authenticate 5 times with wrong finger — verify fallback to device credential
  • Test biometric enrollment change: enroll biometric -> store data -> unenroll -> verify data inaccessible
  • Test dual orientation: Face ID in portrait vs landscape
  • Verify biometric caches correctly cleared on logout
  • Ensure biometric toggle in Settings respected on app relaunch

CI/CD Checklist

  • Build includes correct Info.plist entries (iOS)
  • AndroidManifest includes USE_BIOMETRIC permission
  • ProGuard/R8 rules keep biometric SDK classes
  • Lint against LAContext calls without canEvaluatePolicy guard
  • Automated test: mock biometric success, failure, and lockout paths
  • Accessibility test: biometric with VoiceOver/TalkBack enabled

Session Caching Strategy Decision

How often should user re-authenticate?
├── Per-session (app launch → app background) → Cache on app foreground
│   Re-authenticate on return from background after timeout (5/15/60 min)
│   Valid only for the current app session — logout clears cache
├── Per-action (each sensitive operation) → No caching, require biometric each time
│   Use for: payments, password reveal, account deletion
│   UX: lightweight prompt (no full screen overlay)
├── Time-gated (re-auth every N minutes) → Biometric key with validity duration
│   Android: `setUserAuthenticationValidityDurationSeconds(300)` = 5 min cache
│   iOS: `LAContext.touchIDAuthenticationAllowableReuseDuration = 300`
│   Key remains accessible for duration without re-prompt
└── Device-unlock-gated → Use device credential (PIN) as proxy for biometric
    User has already unlocked phone → trust the device auth
    Low level of assurance — not for sensitive operations

Biometric Enrollment Change Handling

Biometric enrollment changed (new finger/face added, or all removed)?
├── Key invalidated (setInvalidatedByBiometricEnrollment=true, biometryCurrentSet)
│   → Data becomes inaccessible: `KeyPermanentlyInvalidatedException` (Android)
│   → Must re-authenticate + re-encrypt all stored data
│   → Show clear message: "Your biometrics changed, please re-authenticate"
├── Key not invalidated (setInvalidatedByBiometricEnrollment=false)
│   → Old keys work even after new biometric enrolled
│   → Security risk: stolen biometric can decrypt old data
│   → Only use for: non-sensitive preferences, cached UI state
└── All biometrics removed from device
    → Keys remain if not invalidated by enrollment change
    → But no biometric available to unlock them — fallback to device credential
    → Recommend: delete biometric-protected data when no biometric enrolled

Biometric Opt-In UX Patterns

Best practice: never force biometrics on users. Implement a progressive disclosure flow: (a) on first launch, skip biometrics, (b) when user accesses protected feature, show a one-time nudge suggesting biometric setup, (c) if declined, respect the decision for 30 days before another nudge, (d) provide a permanent toggle in Settings to enable/disable, (e) if user disables biometrics, fall back to device credential permanently. The Settings screen should show: enrolled biometric type (Face ID, Fingerprint), enrollment status (X fingerprints, Face enrolled), toggle for each protected feature, and a "Change biometric" button that triggers showBiometric() to re-enroll the key in Keystore. Never show biometric as the only auth option — always have a device credential path.

Code Examples

Android BiometricPrompt with CryptoObject

class SecureStorage(private val context: Context) {
    private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
    private val keyGenerator = KeyGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"
    )

    fun createBiometricKey(keyName: String) {
        val spec = KeyGenParameterSpec.Builder(keyName, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .setUserAuthenticationRequired(true)
            .setUserAuthenticationValidityDurationSeconds(10) // cache 10 seconds
            .setInvalidatedByBiometricEnrollment(true)
            .build()
        keyGenerator.init(spec)
        keyGenerator.generateKey()
    }

    fun encryptWithBiometric(keyName: String, data: String, onSuccess: (ByteArray) -> Unit, onError: (String) -> Unit) {
        val secretKey = keyStore.getKey(keyName, null) as SecretKey
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        cipher.init(Cipher.ENCRYPT_MODE, secretKey)

        val biometricPrompt = BiometricPrompt(
            activity,
            ContextCompat.getMainExecutor(context),
            object : BiometricPrompt.AuthenticationCallback() {
                override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                    val encrypted = cipher.doFinal(data.toByteArray())
                    onSuccess(encrypted)
                }
                override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                    onError(errString.toString())
                }
            }
        )

        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle("Authenticate to access secure data")
            .setSubtitle("Biometric required for decryption")
            .setAllowedAuthenticators(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
            .build()

        biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
    }
}

iOS Keychain with Biometric Protection

import LocalAuthentication
import Security

class BiometricKeychain {
    enum KeychainError: Error {
        case itemNotFound, duplicateItem, unexpectedStatus(OSStatus)
    }

    func storeWithBiometric(service: String, account: String, data: Data) throws {
        let accessControl = SecAccessControlCreateWithFlags(
            nil,
            kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
            [.biometryCurrentSet],
            nil
        )!
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
            kSecValueData as String: data,
            kSecAttrAccessControl as String: accessControl,
            kSecUseAuthenticationContext as String: LAContext()
        ]
        let status = SecItemAdd(query as CFDictionary, nil)
        guard status == errSecSuccess else {
            throw KeychainError.unexpectedStatus(status)
        }
    }

    func readWithBiometric(service: String, account: String) throws -> Data {
        let context = LAContext()
        context.localizedReason = "Authenticate to access your stored credentials"

        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
            kSecReturnData as String: true,
            kSecUseAuthenticationContext as String: context,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]
        var result: AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &result)
        guard status == errSecSuccess else {
            throw KeychainError.unexpectedStatus(status)
        }
        return result as! Data
    }

    func delete(service: String, account: String) throws {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account
        ]
        let status = SecItemDelete(query as CFDictionary)
        guard status == errSecSuccess else {
            throw KeychainError.unexpectedStatus(status)
        }
    }
}

Cross-Platform (React Native)

import ReactNativeBiometrics from 'react-native-biometrics';

const rnBiometrics = new ReactNativeBiometrics();

export async function checkBiometricAvailability() {
  const { available, biometryType } = await rnBiometrics.isSensorAvailable();
  return { available, type: biometryType };
  // biometryType: Biometrics.FaceID | Biometrics.TouchID | Biometrics.Biometrics
}

export async function authenticateWithBiometric(reason: string) {
  const { success } = await rnBiometrics.simplePrompt({ promptMessage: reason });
  return success;
}

export async function createBiometricKey() {
  const { publicKey } = await rnBiometrics.createKeys();
  return publicKey;  // send to server for challenge-response auth
}

export async function signWithBiometric(payload: string) {
  const { success, signature } = await rnBiometrics.createSignature({
    promptMessage: 'Sign in with biometrics',
    payload,
  });
  return { success, signature };
}

Biometric Implementation Anti-Patterns

  • Timeout on biometric key too long: setUserAuthenticationValidityDurationSeconds(86400) = 24 hours. A compromised device exposes data for a full day. Max recommended: 300 seconds (5 min).
  • No biometric enrollment change watcher: Biometric enrollment changes (user adds/removes fingerprint or face) invalidate keys silently. Subscribe to ACTION_BIOMETRIC_ENROLLMENT_CHANGED (Android) or check biometryCurrentSet on each app launch.
  • Fallback to app password without device credential: Users can type an app-specific password instead of device PIN. This creates a secondary auth channel that is often less secure. Always prefer device credential over app password.
  • Biometric-only settings toggle: "Enable Face ID" toggle without showing what features it affects. Users enable it thinking all security is handled, but payments still need device credential. Show a per-feature matrix.
  • Not handling ERROR_VENDOR (Android): Vendor-specific errors (OEM-specific biometric implementations) can crash the app. Never assume only standard error codes. Wrap in try/catch.
  • Face authentication without attention check: On iOS, LAContext.notRequireAttention allows face unlock with closed eyes. For high-sensitivity operations, require attention by relying on the default (attention required).
  • Re-prompting after user cancel: User cancels biometric prompt, app immediately re-shows it — frustrating. After user cancel, wait for explicit user action before re-prompting.
  • Caching biometric success for entire app session: Cache should expire. A user may unlock the app at 9am, hand their phone to someone at 10am — cached biometric access still grants permission. Set session timeout (5-15 min).

Biometric Performance & Battery Considerations

OperationTypical DurationBattery Impact
Face ID recognition~150msMinimal (Neural Engine)
Touch ID recognition~200msMinimal
Biometric key decryption~50msNegligible
Biometric prompt UI show~100msNegligible
Key generation (first time)~200-500msSmall (one-time)
Key invalidation on enrollment change~10msNegligible

Biometric operations are hardware-accelerated on modern devices (Secure Enclave / Titan M). They do not require network access. Battery impact is minimal — comparable to pressing a button. The only power concern is the camera-based Face ID on older iPhones (iPhone X/XR) which uses the TrueDepth camera briefly per authentication.

Biometric Regulatory & Compliance Considerations

GDPR considers biometric data "special category" data requiring explicit consent. Key requirements: (a) biometric data must be processed on-device — never send raw biometric templates to servers, (b) user must explicitly opt-in to biometric auth with clear disclosure of what data is stored, (c) user must be able to revoke biometric consent at any time, (d) on revocation, all biometric-protected keys must be deleted, (e) biometric data must be encrypted at rest (which it is by default in Secure Enclave/Keystore), (f) data retention: biometric templates should not be stored long-term — use device-native biometric APIs that manage templates in the secure processor, (g) transparency: the privacy policy must disclose biometric usage, data storage location, and retention period.

Biometric Testing Matrix

Test ScenarioExpected BehaviorPlatform Differences
Enrolled biometric → successGrant accessBoth platforms
Wrong finger/face → failureShow retry, count attemptsBoth platforms
5 failures → lockoutForce device credentialiOS: locks until device credential used. Android: 30s timeout
No biometric enrolledFallback to device credentialCheck canAuthenticate first
Biometric enrollment changeKey invalidated, data inaccessibleMust re-encrypt data
Device credential enteredGrant access as fallbackBoth
System biometric settings changed during app useCheck status on app return from backgroundBoth
User cancels promptWait for explicit next actionBoth
Call interruption during authResume or retry authHandle onAuthenticationError with ERROR_CANCELED
Accessibility: VoiceOver/TalkBack activeBiometric prompt must be accessibleTest with screen reader enabled

Code Examples — Biometric Session Cache

class BiometricSessionCache {
    private var lastAuthTimestamp: Date?
    private let sessionTimeout: TimeInterval = 300 // 5 minutes

    var isSessionValid: Bool {
        guard let lastAuth = lastAuthTimestamp else { return false }
        return Date().timeIntervalSince(lastAuth) < sessionTimeout
    }

    func authenticate(reason: String, completion: @escaping (Bool) -> Void) {
        if isSessionValid {
            completion(true) // Use cached auth
            return
        }
        let context = LAContext()
        context.localizedReason = reason
        context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
                              localizedReason: reason) { success, error in
            DispatchQueue.main.async {
                if success {
                    self.lastAuthTimestamp = Date()
                }
                completion(success)
            }
        }
    }

    func invalidateSession() {
        lastAuthTimestamp = nil // Called on logout or security event
    }
}

References

  • references/auth-flow.md — Biometric Authentication Flow
  • references/biometric-apis.md — Biometric APIs
  • references/biometric-auth.md — Mobile Biometric Authentication
  • references/biometric-security.md — Biometric Security — Liveness, Spoofing, and Template Protection
  • references/biometric-types.md — Biometric Types
  • references/multi-factor.md — Mobile Multi-Factor Authentication
  • references/secure-storage.md — Secure Storage with Biometrics
  • references/biometrics-fundamentals.md — Biometrics Fundamentals
  • references/biometrics-advanced.md — Advanced Biometric Patterns
  • references/biometrics-testing.md — Biometrics Testing Guide

Handoff

Hand off to mobile-security skill for threat modeling and penetration testing of biometric auth paths.

Implementation Patterns

Observer Pattern for Event Handling

` interface EventObserver { onEvent(event: T): Promise; }

class EventBus { private observers: Set<EventObserver> = new Set(); subscribe(observer: EventObserver): void { this.observers.add(observer); } unsubscribe(observer: EventObserver): void { this.observers.delete(observer); } async emit(event: T): Promise { const results = Array.from(this.observers).map(o => o.onEvent(event)); await Promise.allSettled(results); } } `

Configuration-Driven Approach

config: defaults: timeout: 30s retryCount: 3 overrides: production: timeout: 60s retryCount: 5 development: timeout: 300s retryCount: 1

Production Considerations

Deployment Checklist

  • Configuration validated against schema before startup
  • Health check endpoints registered and monitored
  • Graceful shutdown with draining period (30s timeout)
  • Resource limits configured (CPU, memory, file descriptors)
  • Log level set appropriate for environment
  • Metrics endpoint secured and exposed
  • Rate limiting configured per-tier
  • TLS certificates valid and auto-renewing
  • Database migrations run as separate deployment step
  • Feature flags ready for gradual rollout

Monitoring and Alerting

MetricThresholdSeverityAction
Error rate> 1% over 5minCriticalPage on-call
p99 latency> 2s over 5minWarningInvestigate
Throughput drop> 50% over 1minCriticalCheck upstream
Queue depth> 1000 over 1minWarningScale consumers
Disk usage> 85%WarningClean or expand
Memory usage> 90% heapCriticalRestart or scale

Anti-Patterns

Anti-PatternSymptomRoot CauseSolution
Premature optimizationComplex code for no measured benefitGuessing instead of profilingMeasure first, optimize based on data
Copy-paste reuseDuplicate code across codebaseLack of abstractionExtract shared logic into libraries
Gold-platingFeatures with no current requirementOver-engineeringYAGNI — build what's needed now
Magical thinkingAssumptions without validationSkipping error handlingHandle all failure modes explicitly

Performance Optimization

Caching Strategy

Cache hierarchy: L1 (in-memory local) → L2 (distributed Redis/Memcached) → L3 (CDN/Edge). Cache invalidation: TTL-based (simple, stale), event-based (complex, fresh), write-through (consistent, higher write latency), write-behind (fast writes, eventual consistency).

Resource Pooling

  • Database connections: Pool of reusable connections (HikariCP, pgBouncer)
  • HTTP connections: Keep-alive + connection pooling for external calls
  • Thread pool: Bounded thread pools for async task execution

Profiling Methodology

  1. Establish baseline with production traffic profile
  2. Profile CPU with sampling profiler (pprof, perf, async-profiler)
  3. Profile memory with heap dumps and allocation tracking
  4. Profile I/O with strace/perf trace for syscall analysis
  5. Profile latency with distributed tracing (OpenTelemetry)
  6. Identify bottleneck, formulate hypothesis, implement fix
  7. Re-profile to verify improvement, repeat

Security Considerations

Threat Modeling (STRIDE)

  • Spoofing: Identity validation, authentication
  • Tampering: Integrity checks, digital signatures
  • Repudiation: Audit logs, non-repudiation
  • Information disclosure: Encryption, access control
  • Denial of service: Rate limiting, resource quotas
  • Elevation of privilege: Principle of least privilege

Supply Chain Security

  • Dependency scanning: Snyk, Dependabot, Trivy
  • SBOM generation: CycloneDX or SPDX format
  • Signed commits: GPG or SSH commit signing
  • Artifact verification: Checksum validation, signature verification

Secrets Management

  • Secrets never in code — always in secrets manager (Vault, AWS Secrets Manager)
  • Rotation policy: Rotate database credentials every 90 days
  • Access audit: Log every secrets access, alert on anomalies
  • Encryption at rest and in transit for all secrets
  • Principle of least privilege: each service gets only its own secrets

Rules

  • Default-deny security posture — allow only explicitly required access.
  • All inputs validated, all outputs encoded, all errors handled.
  • Defend in depth — multiple layers of security controls.
  • Fail securely — errors default to safe behavior.
  • Log security-relevant events for audit and investigation.
  • Keep dependencies updated — automate vulnerability scanning.
  • Design for observability from day one, not as an afterthought.
  • Document all architectural decisions with rationale.
  • Review code for security, performance, and correctness before merging.
저장한 Skills