v0.14 is out: a mobile auth pack for Swift/iOS and Android, catching insecure token storage, cleartext traffic, and OAuth in embedded WebViews. Read more →
HIGH AI PREVALENCE: HIGH auth.swift.storage.token-in-userdefaults

A token, secret, or credential is written to UserDefaults.

Why AI tools produce this: AI coding tools generate this anti-pattern by default, it appears in a large share of AI-written auth code.

Why this matters

UserDefaults is an unencrypted plist on disk (readable from an unlocked device backup, a jailbroken device, or the simulator container) and is never an appropriate place for authentication material (CWE-312). AI-generated iOS code frequently reaches for UserDefaults.standard.set(...) because it is the simplest key/value store, silently persisting access/refresh tokens in the clear. App-group suites (UserDefaults(suiteName:)) are just as exposed.

Store secrets in the Keychain instead (via Security framework or a wrapper such as KeychainAccess), e.g. keychain.set(token, key: "authToken"), and keep UserDefaults for non-sensitive preferences only.

VULNERABLE
vulnerable.swift
import Foundation

func persistSession(accessToken: String, refreshToken: String, userName: String) {
    // ruleid: auth.swift.storage.token-in-userdefaults
    UserDefaults.standard.set(accessToken, forKey: "accessToken")

    // ruleid: auth.swift.storage.token-in-userdefaults
    UserDefaults.standard.set(refreshToken, forKey: "userRefresh")

    // ruleid: auth.swift.storage.token-in-userdefaults
    UserDefaults(suiteName: "group.app")!.set("Bearer abc", forKey: "authorization")

    // ruleid: auth.swift.storage.token-in-userdefaults
    UserDefaults.standard.set(apiKeyValue, forKey: "endpointName")
}
SAFE
safe.swift
import Foundation

func persistPreferences(isDark: Bool, launchCount: Int) {
    // Non-sensitive UI preferences are fine in UserDefaults.
    UserDefaults.standard.set(isDark, forKey: "prefersDark")
    UserDefaults.standard.set(launchCount, forKey: "launchCount")
    UserDefaults.standard.set("en_US", forKey: "locale")
}

func persistToken(token: String) {
    // Secrets belong in the Keychain, not UserDefaults.
    keychain.set(token, key: "authToken")
}

Suppressing this rule

If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.

// oauthlint-disable-next-line auth.swift.storage.token-in-userdefaults -- <reason>

References

https://developer.apple.com/documentation/security/keychain_services ↗https://cwe.mitre.org/data/definitions/312.html ↗