agentsclimarketplace

Swift auth security checklist

Skill esaldgut/ai-native-engineering-workspace/global-skills/apple-auth/swift-auth-security-checklist

AI-native engineering workspace — 42 Claude Code agent skills, platform-base workflow docs, and a freshness system that re-verifies each pattern against vendor docs.

Install
npx -y skills add esaldgut/ai-native-engineering-workspace --skill swift-auth-security-checklist

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

A defense-in-depth checklist for iOS auth across storage, transport, lifecycle, and UX — Keychain protection levels (kSecAttrAccessibleWhenUnlockedThisDeviceOnly is canonical; the Always* family is deprecated), token lifecycle (validate JWT `exp` with leeway, NEVER positive `iat` leeway, coalesce refresh), input-validation defense-in-depth, URLError retry buckets, background masking via scenePhase, reinstall hygiene, and OAuth hardening (ASWebAuthenticationSession + PKCE + state — NOT WKWebView, per RFC 8252). Use when designing or reviewing token storage, refresh, or federated sign-in.

SKILL.md

12.4 KB, ~2.5k tokens by cl100k_base, as published. Nobody here has run it

Auth security checklist (iOS, defense-in-depth)

Auth security on iOS is not one decision — it's a chain across where the token lives (Keychain class), how long it lives (lifecycle + refresh), what you let in (input validation), how the network fails (retry buckets), what the App Switcher sees (background masking), and how the user signs in (system browser, not embedded WebView). This skill is the checklist that closes each link, each mapped to an Apple doc or RFC. It is provider-agnostic.

When to invoke

  • You're choosing a Keychain accessibility class for a token, or reviewing one that looks too permissive (kSecAttrAccessibleAlways, plain kSecAttrAccessibleWhenUnlocked for a device-bound secret).
  • You're implementing token refresh, JWT validation, or graceful degradation on network failure.
  • You're adding federated/OAuth sign-in and need the system-browser + PKCE + state shape.
  • You're hardening the app against shoulder-surfing / App Switcher snapshots.

Announce on invoke: "Using swift-auth-security-checklist to apply the iOS auth defense-in-depth checklist (Keychain class, token lifecycle, OAuth via ASWebAuthenticationSession) per Apple docs + RFC 8252."

Do not use this as a crypto reference — for constant-time comparison, HPKE, ML-KEM, and pinning internals, defer to swift-post-quantum-security-ios26. This skill is about configuration and lifecycle, not primitive selection.

The checklist

LinkCanonical choice (verified)Anti-pattern to reject
Token storage classkSecClassGenericPassword + kSecAttrAccessibleWhenUnlockedThisDeviceOnlyUserDefaults; kSecAttrAccessibleAlways (deprecated); plain WhenUnlocked for device-bound secrets
Biometric-bound (L2)SecAccessControlCreateWithFlags(..., .biometryCurrentSet, ...) for refresh tokensgating with app-level passcode in UserDefaults
JWT validationcheck exp with 30–60 s leeway; negative-only iat leeway; verify iss/audaccepting future-dated tokens (positive iat leeway)
Refreshsingle-flight via an actor caching the in-flight Taskone network refresh per concurrent caller (thundering herd)
Graceful degradationnotConnectedToInternet → keep UI, disable mutations; 401/invalid_grant → sign outsigning out on a transient network blip
Input validationbound length, reject NUL/control chars, normalize Unicode (NFKC) before sendtrusting client validation as authoritative
Network retryretry-with-backoff vs no-retry buckets (below); honor Retry-After on 429retrying 4xx; retrying a user-cancelled auth
Background maskingswap to an opaque placeholder when scenePhase != .activeleaving token-bearing UI in the App Switcher snapshot
Reinstall hygienefirst-launch UserDefaults sentinel; purge stale Keychain on missing sentinelinheriting a previous install's tokens silently
OAuth flowASWebAuthenticationSession + PKCE (S256) + stateWKWebView / SFSafariViewController for the auth step

The rules

1. kSecAttrAccessibleWhenUnlockedThisDeviceOnly is the default for tokens

Apple's docs: items with this class do not migrate to a new device and are absent after restoring another device's backup — exactly the property you want for a session token. The Always* family is deprecated (removed in iOS 12); never recommend it. If a token genuinely must be readable before first unlock, use the AfterFirstUnlockThisDeviceOnly class — not Always. For refresh tokens, layer SecAccessControlCreateWithFlags with .biometryCurrentSet so the item invalidates on biometric enrollment changes.

2. JWT iat leeway must be negative-only

exp gets a small positive leeway (30–60 s, clock skew). iat must never get positive leeway — accepting a future-dated iat opens a replay window. Allow slightly-past iat only.

3. Coalesce refresh — one in-flight Task, shared by all callers

A burst of 401s must trigger exactly one refresh. Cache the in-flight refresh Task inside an actor; concurrent callers await the same task and receive the same new token. (The audit suite proves this; the perf suite proves it doesn't cost N×.)

4. Use the system browser for OAuth — never an embedded WebView

RFC 8252 (OAuth 2.0 for Native Apps) and Apple's guidance both require the system browser for the authorization step. Use ASWebAuthenticationSession: it shares the system cookie jar (enabling SSO and fewer password prompts) and guarantees only your app receives the callback. WKWebView is for non-auth web content only. Set prefersEphemeralWebBrowserSession = true (default is false) only for flows the user explicitly wants isolated. Always send PKCE and state.

5. Mask sensitive UI in the background

The App Switcher snapshots your foreground view. In SwiftUI, read @Environment(\.scenePhase) and overlay an opaque placeholder when it isn't .active. (UIKit: observe UIApplication.willResignActiveNotification.)

Canonical example

import Foundation
import Security
import SwiftUI

// 1. Token storage — canonical class, ThisDeviceOnly.
struct SecureTokenStore {
    let service: String, account: String
    func save(_ token: Data) throws {
        let query: [String: Any] = [
            kSecClass as String:            kSecClassGenericPassword,
            kSecAttrService as String:      service,
            kSecAttrAccount as String:      account,
            kSecValueData as String:        token,
            kSecAttrAccessible as String:   kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
        ]
        SecItemDelete(query as CFDictionary)
        let status = SecItemAdd(query as CFDictionary, nil)
        guard status == errSecSuccess else { throw KeychainError.status(status) }
    }
}

// 2. Network retry classification — transient (backoff) vs terminal.
extension URLError {
    var isTransientRetryable: Bool {
        switch code {
        case .timedOut, .networkConnectionLost, .dnsLookupFailed,
             .cannotConnectToHost, .notConnectedToInternet: return true
        default: return false   // .userCancelledAuthentication, .badServerResponse, .cancelled → NO retry
        }
    }
}

// 3. Single-flight refresh.
actor TokenRefresher {
    private var inFlight: Task<String, Error>?
    func freshToken(using refresh: @escaping () async throws -> String) async throws -> String {
        if let inFlight { return try await inFlight.value }     // coalesce
        let task = Task { try await refresh() }
        inFlight = task
        defer { inFlight = nil }
        return try await task.value
    }
}

// 4. Background masking via scenePhase.
struct RootView: View {
    @Environment(\.scenePhase) private var scenePhase
    var body: some View {
        ZStack {
            MainAppView()
            if scenePhase != .active {
                Color(.systemBackground).overlay(Image(systemName: "lock.fill"))  // App Switcher safe
            }
        }
    }
}

// 5. OAuth via the SYSTEM browser — not WKWebView.
func startOAuth(authURL: URL, callbackScheme: String,
                anchor: ASPresentationAnchor) {
    let session = ASWebAuthenticationSession(url: authURL,           // authURL carries PKCE S256 + state
                                             callbackURLScheme: callbackScheme) { callback, error in
        // validate `state` (constant-time) + exchange `code` with PKCE verifier
    }
    session.presentationContextProvider = PresentationProvider(anchor: anchor)
    session.prefersEphemeralWebBrowserSession = false   // default; set true only for isolated flows
    session.start()
}

Decision aid: graceful degradation on refresh failure

  • URLError.notConnectedToInternet / transient → keep the session, disable mutating actions, retry with backoff.
  • HTTP 401 / invalid_grant → the refresh token is dead → sign out, route to login.
  • HTTP 429 → back off, honor Retry-After.
  • 4xx other than 401 → no retry (server is authoritative); surface a generic error.

Related skills

  • global-skills/apple-auth/swift-post-quantum-security-ios26/SKILL.md — constant-time state/MAC comparison and certificate pinning that this checklist references.
  • global-skills/apple-auth/swift-auth-security-audit-suite/SKILL.md — the tests that prove each checklist link (storage class, single-flight, anti-enumeration, first-launch purge).
  • global-skills/apple/apple-anti-patterns/SKILL.md — registers "WKWebView for OAuth" and "kSecAttrAccessibleAlways" as anti-patterns this checklist rejects.

Sources


Last verified: 2026-06-03 against Apple Security/AuthenticationServices/SwiftUI docs (live) + RFC 8252. kSecAttrAccessibleWhenUnlockedThisDeviceOnly confirmed "does not migrate to a new device"; prefersEphemeralWebBrowserSession confirmed default false. kSecAttrAccessibleAlways is deprecated and WKWebView for OAuth violates RFC 8252 — both guarded against here. Re-check after: WWDC26 + any CryptoKit/Security release, or by 2026-12-01. Decay risk: low. Found a drift? Run /skill-pattern-freshness-audit apple-auth.

Gives 0 of the 12 instructions most security skills give in ~2.5k tokens

Counted across 648 of the 828 authors here whose files we hold, read 2026-08-06

  • parameterize all database queriesin 67 of 648, across 49 files
  • hash passwords using bcrypt scrypt or argon2in 48 of 648, across 35 files
  • apply rate limiting to authentication endpointsin 48 of 648, across 24 files
  • Configure security headersin 35 of 648, across 18 files
  • validate all inputsin 32 of 648, across 24 files
  • validate all external input at the system boundaryin 29 of 648, across 18 files
  • run containers as a non-root userin 28 of 648, across 15 files
  • use httponly secure samesite cookies for sessionsin 26 of 648, across 15 files
  • run dependency audits before every releasein 21 of 648, across 10 files
  • encode output to prevent cross-site scriptingin 21 of 648, across 10 files
  • copy dependencies before source codein 20 of 648, across 9 files
  • store secrets in environment variablesin 20 of 648, across 17 files

Said here and by no other author read

  • Store tokens using ThisDeviceOnly accessibility classes
  • Bind refresh tokens with biometric access control flags
  • Validate JWT exp with positive leeway
  • Coalesce concurrent token refreshes into one task
  • Use the system browser for OAuth authentication flows
  • Mask sensitive UI when scene phase is inactive

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.