All skills
dpearson2699 avatar

/authentication

@45c9085

Implement iOS authentication flows with AuthenticationServices and LocalAuthentication. Use when building Sign in with Apple, passkey/WebAuthn registration or sign-in with ASAuthorizationPlatformPublicKeyCredentialProvider, ASAuthorizationController credential state and revocation handling, ASWebAuthenticationSession OAuth or third-party login, Password AutoFill, identity-token server validation, or local biometric re-authentication with LAContext.

Use this Skill: https://skilld.dev/gh/dpearson2699/swift-ios-skills/authentication

This session only. Nothing lands on disk.

referenceskeychain-biometric.md

≈2.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Keychain Token Storage & Biometric Authentication

Self-contained reference for storing authentication tokens in Keychain and protecting them with Keychain-bound biometric authentication (Face ID / Touch ID). Covers the patterns most commonly needed alongside Sign in with Apple, passkey, and OAuth flows.

This is an authentication-adjacent quick reference, not a full security architecture guide. Route Keychain migration, access-control policy design, CryptoKit encryption, Secure Enclave keys, certificate pinning, keychain sharing, storage-hardening strategy, and OWASP MASVS/MASTG mapping to swift-security.

Contents

Storing Tokens in Keychain

The Keychain is the ONLY correct place to store tokens, passwords, API keys, or secrets. Never store these in UserDefaults, files, or Core Data.

func saveToKeychain(account: String, data: Data, service: String) throws {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account,
        kSecAttrService as String: service,
        kSecValueData as String: data,
        kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
    ]

    let status = SecItemAdd(query as CFDictionary, nil)

    if status == errSecDuplicateItem {
        let updateQuery: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: account,
            kSecAttrService as String: service
        ]
        let updates: [String: Any] = [kSecValueData as String: data]
        let updateStatus = SecItemUpdate(updateQuery as CFDictionary, updates as CFDictionary)
        guard updateStatus == errSecSuccess else {
            throw KeychainError.updateFailed(updateStatus)
        }
    } else if status != errSecSuccess {
        throw KeychainError.saveFailed(status)
    }
}

Reading Tokens from Keychain

func readFromKeychain(account: String, service: String) throws -> Data {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account,
        kSecAttrService as String: service,
        kSecReturnData as String: true,
        kSecMatchLimit as String: kSecMatchLimitOne
    ]

    var result: AnyObject?
    let status = SecItemCopyMatching(query as CFDictionary, &result)

    guard status == errSecSuccess, let data = result as? Data else {
        throw KeychainError.readFailed(status)
    }
    return data
}

Deleting Tokens from Keychain

func deleteFromKeychain(account: String, service: String) throws {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account,
        kSecAttrService as String: service
    ]

    let status = SecItemDelete(query as CFDictionary)
    guard status == errSecSuccess || status == errSecItemNotFound else {
        throw KeychainError.deleteFailed(status)
    }
}

Use a ThisDeviceOnly accessibility class for sensitive app credentials unless the product explicitly requires restore or sharing behavior. Choose broader Keychain accessibility, migration, or sharing strategy in swift-security, not in this authentication skill.

Do not use kSecAttrAccessibleAlways for app credentials; Apple marks it as not recommended for application use because items remain accessible regardless of lock state.

Biometric Authentication with LAContext

Use LAContext from LocalAuthentication for Face ID / Touch ID prompts before showing sensitive screens or performing protected actions. This is a local interaction gate, not proof that a stored secret is safe to release. For tokens, private keys, or high-value credentials, bind access to the Keychain item with SecAccessControl and let SecItemCopyMatching perform the authentication.

import LocalAuthentication

func authenticateWithBiometrics() async throws -> Bool {
    let context = LAContext()
    var error: NSError?

    guard context.canEvaluatePolicy(
        .deviceOwnerAuthenticationWithBiometrics, error: &error
    ) else {
        // Biometrics not available -- fall back to passcode
        if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
            return try await context.evaluatePolicy(
                .deviceOwnerAuthentication,
                localizedReason: "Authenticate to access your account"
            )
        }
        throw AuthError.biometricsUnavailable
    }

    return try await context.evaluatePolicy(
        .deviceOwnerAuthenticationWithBiometrics,
        localizedReason: "Authenticate to access your account"
    )
}

Info.plist Requirement

You MUST include NSFaceIDUsageDescription in Info.plist:

<key>NSFaceIDUsageDescription</key>
<string>Authenticate to access your secure data</string>

Missing this key causes a crash on Face ID devices.

LAContext Configuration

let context = LAContext()
context.localizedFallbackTitle = "Use Passcode"
context.touchIDAuthenticationAllowableReuseDuration = 30
let currentState = context.evaluatedPolicyDomainState // Compare to detect enrollment changes

Biometric-Protected Keychain Items

Protect keychain items so they require biometric authentication to read:

let access = SecAccessControlCreateWithFlags(
    nil,
    kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
    .biometryCurrentSet,
    nil
)!

let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: "auth-token",
    kSecValueData as String: tokenData,
    kSecAttrAccessControl as String: access
]

Read the protected item with an authentication context so Keychain, not app logic, controls release of the secret:

let context = LAContext()
context.localizedReason = "Authenticate to access your account"

let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: "auth-token",
    kSecReturnData as String: true,
    kSecMatchLimit as String: kSecMatchLimitOne,
    kSecUseAuthenticationContext as String: context
]

var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess, let tokenData = result as? Data else {
    throw KeychainError.readFailed(status)
}

Use .biometryCurrentSet when an auth token must be invalidated after biometric enrollment changes. Route broader SecAccessControl flag selection and policy tradeoffs to swift-security.

Keychain Error Handling

enum KeychainError: Error {
    case saveFailed(OSStatus)
    case updateFailed(OSStatus)
    case readFailed(OSStatus)
    case deleteFailed(OSStatus)

    var localizedDescription: String {
        switch self {
        case .saveFailed(let status),
             .updateFailed(let status),
             .readFailed(let status),
             .deleteFailed(let status):
            return SecCopyErrorMessageString(status, nil) as String? ?? "Unknown error"
        }
    }
}

References

Source: SKILL.md on GitHub

2 warnings16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill provides instructions and code for implementing iOS authentication flows using AuthenticationServices and LocalAuthentication. It follows security best practices, such as storing tokens in the Keychain and mandating server-side validation of identity tokens. A low-risk surface for indirect prompt injection is identified because the skill processes authentication data and redirect URLs from external providers.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    2/2 files flagged

  • ZeroLeaks5mo

    2 findings · Score: 76/100

Signed by skilld at 45c9085. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Steadyupdated 3 months ago
  • swift
  • ios
  • authentication
  • sign-in-with-apple
  • oauth
  • biometric
  • authenticationservices
  • keychain
  • autofill

README badge

README badge for dpearson2699/swift-ios-skills/authentication

Implements iOS authentication using AuthenticationServices: Sign in with Apple with credential state checking, OAuth flows via ASWebAuthenticationSession, Password AutoFill, and biometric authentication. Covers the full lifecycle from initial authorization through token validation, credential revocation handling, and server-side JWT verification.

Generated from the current SKILL.md.

Does this skill cover Sign in with Apple setup?
Yes. The skill includes ASAuthorizationAppleIDProvider, ASAuthorizationController, and delegate patterns for handling Apple ID credentials, identity tokens, and authorization codes.
How do I check if a user has revoked Sign in with Apple access?
Use ASAuthorizationAppleIDProvider.credentialState(forUserID:) on app launch, and register for ASAuthorizationAppleIDProvider.credentialRevokedNotification to detect revocation in real time.
Does this support OAuth flows with third-party providers?
Yes. The skill includes ASWebAuthenticationSession patterns for OAuth and third-party authentication (Google, GitHub, etc.) with both custom scheme and universal link callbacks.
Can I use Password AutoFill alongside Sign in with Apple?
Yes. The skill shows how to combine ASAuthorizationPasswordProvider and ASAuthorizationAppleIDProvider in a single ASAuthorizationController for offering both credential types.
Does this include biometric authentication?
The skill mentions LAContext for biometric authentication and references a keychain-biometric.md guide, but detailed biometric patterns are not included in the main content.

Generated from the current SKILL.md. These answers refresh after source changes.