Complete reference of Apple's passkey error codes across macOS 26.2 and iOS 26.2. Maps ASCAuthorizationError to ASAuthorizationError to DOMException.
Vincent
Created: March 5, 2026
Updated: March 5, 2026

Apple's passkey error system has multiple layers and most developers only see the surface. The public ASAuthorizationError codes (1000-1010) are intentionally broad - Apple compresses dozens of specific failure modes into a handful of categories before they reach your app or Safari.
Want to find out how many people use passkeys?
Under the hood, Apple's passkey error stack is a 3-layer translation pipeline: specific codes from the AuthenticationServicesAgent daemon, translated to public ASAuthorizationError codes in AuthenticationServices, then translated again to DOMExceptions for Safari. At each layer, information is lost. This article maps every layer completely.
This reference covers Apple's error codes as observed in macOS 26.2 (Tahoe), iOS 26.2 and iOS 18.4 simulator runtimes. The error code tables are identical between iOS and macOS.
What this article covers vs related articles:
NotAllowedError and AbortError): see WebAuthn Errors in ProductionEvery passkey error on iOS flows through a 3-layer pipeline before reaching your app or Safari. Understanding these layers explains why logging only ASAuthorizationErrorFailed can lead to missing the actual failure reason.
| Layer | Component | Error domain | What happens |
|---|---|---|---|
| 1 | AuthenticationServicesAgent (XPC daemon) | ASCAuthorizationErrorDomain (codes 0-22) | Generates specific errors with user-visible messages like "Cannot create a passkey. iCloud Keychain is off." |
| 2 | ASAuthorizationController (public framework) | ASAuthorizationErrorDomain (codes 1000-1010) | Translates ASCAuthorizationError to public codes. Many codes (4-11, 13, 20-21) have no direct mapping and are wrapped into Failed (1004) with the original preserved in NSUnderlyingErrorKey |
| 3a | Safari/WebKit | DOMException | Translates ASAuthorizationError to DOMExceptions. Only three codes get specific handling: 1001 → NotAllowedError, 1006 → InvalidStateError, 1004 + security error → SecurityError. Everything else becomes generic NotAllowedError |
| 3b | Native apps | ASAuthorizationErrorDomain (codes 1000-1010) | Receives the full set of public ASAuthorizationError codes. On iOS 18+, most common failures have dedicated codes (1005, 1006, 1010) that need no unwrapping. For code 1004, check NSUnderlyingErrorKey when present for additional detail |
ASAuthorizationErrorFailed (1004), check NSUnderlyingErrorKey for the underlying ASCAuthorizationErrorDomain code when present - but note that some 1004 errors (e.g. domain association failures) report details in localizedDescription instead, with NSUnderlyingErrorKey nil. Websites running in Safari lose all this detail because WebKit translates errors to DOMExceptions before exposing them to JavaScript.Want to experiment with passkey flows? Try our Passkeys Debugger.
The public error domain that native apps and Safari see. 11 codes, stable across iOS versions with new additions.
| Code | Name | Since | What it means |
|---|---|---|---|
| 1000 | Unknown | iOS 13 | Unknown error. Should not appear in production |
| 1001 | Canceled | iOS 13 | User dismissed the passkey sheet. The most common error. On iOS, this is a clean signal unlike Android where user cancel is harder to distinguish |
| 1002 | InvalidResponse | iOS 13 | The authorization response was invalid. Rare - indicates framework-level corruption |
| 1003 | NotHandled | iOS 13 | The authorization request was not handled by any provider. Check your entitlements and configuration |
| 1004 | Failed | iOS 13 | Generic failure. Check NSUnderlyingErrorKey for additional detail when present, but note that some 1004 errors (e.g. domain association failures) report details in localizedDescription instead. On iOS 18+, many scenarios that previously produced 1004 now have dedicated codes (1006, 1010) |
| 1005 | NotInteractive | iOS 15 | UI is required but the request was made with preferImmediatelyAvailableCredentials. This is the "no credential available" signal - the iOS equivalent of a clean "not found" |
| 1006 | MatchedExcludedCredential | iOS 18 | A credential in the excludeCredentials list already exists on this device. Clean signal for duplicate detection |
| 1007 | CredentialImport | iOS 18.2 | Credential import operation failed |
| 1008 | CredentialExport | iOS 18.2 | Credential export operation failed |
| 1009 | PreferSignInWithApple | iOS 26 | User prefers to use Sign in with Apple instead of a passkey |
| 1010 | deviceNotConfiguredForPasskeyCreation | iOS 26 | New in iOS 26. Device lacks passcode or iCloud Keychain configuration required for passkey creation. Previously this was hidden inside code 1004 |
Apple has been systematically extracting specific failure modes out of the generic Failed (1004) bucket across iOS versions. This is the most important practical trend for passkey error handling:
| iOS version | What was extracted from 1004 | New dedicated code | Impact |
|---|---|---|---|
| iOS 15 | No credentials (preferImmediatelyAvailableCredentials) | 1005 | No longer need to string-match "no credentials" on 1004 |
| iOS 18 | Duplicate credential (excludeCredentials match) | 1006 | No longer need WKErrorDomain code 8 fallback or string matching |
| iOS 26 | Device not configured (no passcode, iCloud Keychain off) | 1010 | No longer need to string-match "iCloud Keychain is off" on 1004 |
On iOS 26, the remaining cases that still need 1004 + NSUnderlyingErrorKey unwrapping are increasingly narrow: primarily security key operations (ASC codes 4, 8, 9, 10, 13, 20, 21) which require physical hardware. Apple is gradually solving the information-loss problem at the API level rather than requiring developers to dig into private error domains.
When you receive an ASAuthorizationError, several userInfo keys carry diagnostic information:
| Key | Status | What it contains |
|---|---|---|
NSUnderlyingErrorKey | Public API | The underlying error when present. Check on code 1004 but do not depend on it being populated - our testing found it nil for domain association failures. Most reliably present for WebKit-originated errors and security key operations |
NSMultipleUnderlyingErrorsKey | Public API | Array of underlying errors - some ASAuthorization failures use this instead of the single-error key |
NSLocalizedFailureReasonErrorKey | Public API | Localized failure reason string. Often contains the actual diagnostic detail for code 1004 (e.g. "Application with identifier X is not associated with domain Y"). More reliably populated than NSUnderlyingErrorKey in our testing |
ASAuthorizationErrorUserVisibleMessageKey | Framework-specific | Human-readable message from the framework. Log when present but do not depend on it for user-facing messages - it may be absent or change across OS versions |
ASExtensionLocalizedFailureReasonErrorKey | Framework-specific | Localized failure reason from a credential provider extension (e.g. 1Password, Dashlane) |
Inspecting NSUnderlyingErrorKey is a standard iOS error handling practice, not something specific to passkeys. Many iOS SDKs use this two-level unwrap pattern where the public error code gives you the category and the underlying error gives you the root cause:
userInfo dictionary. NSUnderlyingErrorKey contains the underlying error that caused the error in question."NSError, reading userInfo[NSUnderlyingErrorKey] and handling @unknown default across every error callbackNSUnderlyingErrorKeyFileManager where the surface error hides the actual POSIX causeIn theory, this same pattern should help dig deeper into passkey failures - inspecting the underlying ASCAuthorizationError behind a generic ASAuthorizationErrorFailed (1004) could reveal whether the cause is "iCloud Keychain is off" or "security key PIN is locked." In practice however, our POC testing on iOS 26.2 found that NSUnderlyingErrorKey was nil for every native passkey scenario we triggered (domain association failures, missing credentials). The diagnostic detail was in localizedDescription and NSLocalizedFailureReasonErrorKey instead. This suggests that while NSUnderlyingErrorKey is well-established in other Apple frameworks, passkey errors on modern iOS rely more on dedicated public codes and localized description strings than on the underlying error chain.
The private error domain generated by Apple's AuthenticationServicesAgent XPC daemon. These codes are expected to surface via NSUnderlyingErrorKey at runtime based on disassembly analysis, though runtime confirmation is limited to WebKit-originated and device-configuration error paths. The following table covers codes present in macOS 26.2.
Note that ASC code 5 (no credentials) and code 7 (duplicate credential) - the two most common "interesting" codes - are now surfaced via dedicated public codes on modern iOS: code 5's scenario is handled by 1005 (NotInteractive) when using preferImmediatelyAvailableCredentials, and code 7's scenario is handled by 1006 (MatchedExcludedCredential) on iOS 18+. The primary value of this ASCAuthorizationError table is for security key debugging and understanding older iOS behavior.
| Code | User-visible message | Category | What it means |
|---|---|---|---|
| 0 | (no description) | Unknown | Unknown error |
| 1 | (no description) | General failure | Generic failure |
| 2 | (no description) | User canceled | User canceled at the daemon layer |
| 4 | "Too many NFC devices found. Please present only one NFC device." | NFC / security key | Multiple NFC security keys detected simultaneously |
| 5 | "Found no credentials on this device." | No credentials | No passkey exists for this RP on this device. Important diagnostic signal that surfaces as generic 1004 to apps that don't check NSUnderlyingErrorKey |
| 6 | "The operation cannot be completed." | General failure | Generic operation failure |
| 7 | (biometry-dependent, see below) | Duplicate credential | A passkey already exists for this account |
| 8 | "Unrecognized PIN code. Please try again." | Security key PIN | Incorrect PIN entered for a hardware security key |
| 9 | "Your security key is temporarily locked. Please remove it and try again." | Security key locked | Too many recent PIN failures, temporary lockout |
| 10 | "Too many invalid PIN attempts. Your security key must be reset." | Security key PIN lockout | Permanent PIN lockout requiring security key reset |
| 11 | (returns nil) | Unused/reserved | No description assigned |
| 12 | (returns nil) | Cancel variant | Mapped to ASAuthorizationErrorCanceled (1001), no message needed |
| 13 | "Your security key's storage is full." | Security key full | The FIDO2 security key has no remaining credential slots |
| 14 | (no description) | Invalid response | Invalid response from the authenticator |
| 17 | (no description) | Device not configured | Device not configured for passkey creation (maps to 1010 on iOS 26+) |
| 20 | "Pin entered was too short. Please choose a longer one." | Security key PIN | PIN length validation failure (too short) |
| 21 | "Pin entered was too long. Please choose a shorter one." | Security key PIN | PIN length validation failure (too long) |
| 22 | (no description) | Device not configured | Second device-not-configured variant (also maps to 1010) |
Code 7 has three distinct code paths based on runtime device state:
| Condition | Message |
|---|---|
shouldUseAlternateCredentialStore=true | "You already have a passkey for this account in your iCloud Keychain." |
biometryType == 2 (Face ID) | "You have already set up Face ID for this website." |
biometryType != 2 (Touch ID / other) | "You have already set up Touch ID for this website." |

Authentication Analytics Whitepaper:
Track passkey adoption & impact on revenue.
The 3-layer translation is the core of Apple's error pipeline. Here is the complete mapping from ASCAuthorizationError to public ASAuthorizationError as observed in macOS 26.2 and iOS 26.2.
| ASC Code | Public Code | Public Name | Information lost? |
|---|---|---|---|
| 0 | 1000 | Unknown | No |
| 1 | 1004 | Failed | Yes - generic wrapper |
| 2 | 1001 | Canceled | No |
| 12 | 1001 | Canceled | No |
| 14 | 1002 | InvalidResponse | No |
| 17 | 1010 | DeviceNotConfiguredForPasskey | No (iOS 26+) |
| 22 | 1010 | DeviceNotConfiguredForPasskey | No (iOS 26+) |
| 4-11, 13, 20-21 | nil | → wrapped in Failed (1004) | These codes return nil from _convertCoreErrorToPublicError: and are inferred to be wrapped in Failed (1004) with NSUnderlyingErrorKey. Confirmed for the WebKit path; inferred for the native ASC daemon path |
Safari and native apps receive the same ASAuthorizationError from Layer 2, but Safari then translates it to a DOMException (Layer 3a) while native apps retain the full public code set. Codes 1005, 1006, 1009 and 1010 are native-app-only signals that Safari collapses into generic DOMExceptions.
| ASAuthorizationError | Code | DOMException | Notes |
|---|---|---|---|
Canceled | 1001 | NotAllowedError | User dismissed the sheet. Cannot be distinguished from other NotAllowedErrors in JavaScript |
MatchedExcludedCredential | 1006 | InvalidStateError | Clean signal for excludeCredentials |
Failed + ASCAuthorizationErrorSecurityError underlying | 1004 | SecurityError | RP ID / origin mismatch |
Failed + other underlying | 1004 | NotAllowedError | Generic fallback |
Unknown | 1000 | ASSERT_NOT_REACHED | Should never happen |
InvalidResponse | 1002 | ASSERT_NOT_REACHED | Should never happen |
NotHandled | 1003 | ASSERT_NOT_REACHED | Should never happen |
NotInteractive | 1005 | ASSERT_NOT_REACHED | Should never happen |
DeviceNotConfiguredForPasskey | 1010 | (not handled) | Falls through - new code not yet in WebKit translation |
Subscribe to our Passkeys Substack for the latest news.
Based on analysis of production passkey SDKs, these are the most common mistakes in Apple passkey error handling:
localizedDescription for error classification. Some SDKs use patterns like error.localizedDescription.contains("No credentials available for login.") or error.localizedDescription.contains("is not associated with domain"). This only works on English-language devices. Apple localizes these messages into 30+ languages. Use error codes for classification, not strings.default: → .cancelled in their switch statement, which means codes 1005, 1009, 1010 all silently become "user cancelled." Unknown codes should map to "unknown" not "cancelled" - a code you don't recognize is not the same as a user dismissal.default instead of @unknown default. Plain default silences the compiler when Apple adds new codes (which they've done in every major release recently: iOS 15 added 1005, iOS 18 added 1006-1008, iOS 26 added 1009-1010). Using @unknown default gives you a compiler warning on SDK upgrade, prompting you to handle new cases explicitly. The runtime behavior is identical.A simple example of handling codes structurally, with #available checks for newer codes and string matching only as a last resort for the ambiguous 1001 case:
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { guard let authorizationError = error as? ASAuthorizationError else { handleUnknownError(error) return } // iOS 18+: dedicated code for duplicate credential if #available(iOS 18.0, macOS 15.0, *) { if authorizationError.code == .matchedExcludedCredential { handleDuplicateCredential() return } } // iOS 26+: dedicated codes for device config and Sign in with Apple preference if #available(iOS 26.0, macOS 26.0, *) { if authorizationError.code == .deviceNotConfiguredForPasskeyCreation { handleDeviceNotConfigured() return } if authorizationError.code == .preferSignInWithApple { handlePreferSignInWithApple() return } } switch authorizationError.code { case .canceled: // 1001 // On older iOS without preferImmediately, "no credentials" // can arrive as 1001 with a localized message. // String matching is a pragmatic last resort for this ambiguous case. if error.localizedDescription.contains("No credentials available") { handleNoCredentials() } else { handleUserCancelled() } case .notInteractive: // 1005 (iOS 15+) // Clean signal: no credentials on this device for this RP. // No UI was shown. Offer passkey creation or fall back to password. handleNoCredentials() case .failed: // 1004 // Generic bucket. The localizedDescription contains the actual reason. // e.g. "Application with identifier X is not associated with domain Y" if error.localizedDescription.contains("not associated with domain") { handleDomainNotAssociated() } else { // Check NSUnderlyingErrorKey for security key details let nsError = error as NSError if let underlying = nsError.userInfo[NSUnderlyingErrorKey] as? NSError, underlying.domain == "ASCAuthorizationErrorDomain" { handleASCError(code: underlying.code) } else { handleGenericFailure(error) } } case .invalidResponse, .notHandled, .unknown: handleUnknownError(error) @unknown default: // IMPORTANT: do NOT map unknown codes to "cancelled". // @unknown default triggers a compiler warning when Apple adds new codes. handleUnknownError(error) } }
Biometric failures fire before passkey operations complete. These errors come from LAContext and surface as part of the passkey flow when user verification is required.
| Code | Name | Since | What it means |
|---|---|---|---|
| -1 | AuthenticationFailed | iOS 8 | User failed biometric or passcode verification |
| -2 | UserCancel | iOS 8 | User tapped Cancel on the biometric prompt |
| -3 | UserFallback | iOS 8 | User tapped "Enter Password" to use passcode instead of biometric |
| -4 | SystemCancel | iOS 8 | System canceled (app went to background, incoming call) |
| -5 | PasscodeNotSet | iOS 8 | No passcode configured. Passkeys require this. Related to ASCAuthorizationError 17/22 |
| -6 | BiometryNotAvailable | iOS 11 | Biometric hardware not available on this device |
| -7 | BiometryNotEnrolled | iOS 11 | No Face ID or Touch ID enrolled. Passkeys still work with passcode fallback |
| -8 | BiometryLockout | iOS 11 | Too many failed biometric attempts. Device requires passcode to unlock biometry |
| -9 | AppCancel | iOS 9 | Your app called invalidate() during authentication |
| -10 | InvalidContext | iOS 9 | The LAContext was previously invalidated |
| -11 | BiometryNotPaired | macOS 11.2 | Removable biometric accessory not paired (macOS only) |
| -12 | BiometryDisconnected | macOS 11.2 | Removable biometric accessory disconnected (macOS only) |
| -14 | CompanionNotAvailable | iOS 18 | No paired companion device (e.g. Apple Watch) nearby for authentication |
| -1004 | NotInteractive | iOS 8 | UI required but interactionNotAllowed was set to true |
In practice, the most important LA errors for passkey reliability are -5 (no passcode - passkeys cannot work), -8 (biometric lockout - temporary, passcode fallback available) and -4 (system cancel - transient, retry appropriate).
Beyond the structured error codes, the Apple frameworks contain dozens of diagnostic strings that appear in logs, crash reports and error userInfo dictionaries.
| String | Context |
|---|---|
| "Cannot create a passkey. iCloud Keychain is off." | Pre-flight check, surfaces as code 1004 |
| "To save a passkey, you need to enable iCloud Keychain." | User-facing prompt text |
| "Passkeys are unavailable because iCloud Keychain has been disabled by a configuration profile." | MDM-managed devices |
| "Passkeys require a passcode and work best with Face ID." | Device setup guidance (iPhone) |
| "Passkeys require a passcode and work best with Touch ID." | Device setup guidance (iPad, Mac) |
| "Passkeys require a passcode and work best with Optic ID." | Device setup guidance (Apple Vision Pro, new in iOS 26) |
| "Passkeys require a passcode." | Device without biometric hardware |
| String | Context |
|---|---|
| "TCC access denied for browser passkey request." | Transparency, Consent and Control permission denied |
| "Rejecting unentitled process from requesting passkeys." | Missing app entitlement |
| "Client is missing web browser entitlement." | Browser-specific entitlement check |
| "Dropping passkey requests from quirked relying party." | Apple maintains an RP quirks list |
Get free passkey whitepaper for enterprises.
Always log beyond the ASAuthorizationError code. Three practical points: (1) for many 1004 errors, localizedDescription contains the actual diagnostic (e.g. "Application with identifier X is not associated with domain Y") - not NSUnderlyingErrorKey, which may be nil; (2) AuthenticationServices failures sometimes contain NSMultipleUnderlyingErrorsKey instead of a single NSUnderlyingErrorKey; and (3) NSError objects in userInfo are not JSON-serializable - convert to strings or dictionaries before sending to analytics.
func logPasskeyError(_ error: Error, operation: String) { let nsError = error as NSError var logEntry: [String: Any] = [ "operation": operation, "domain": nsError.domain, "code": nsError.code, "description": String(describing: error) ] if let failureReason = nsError.localizedFailureReason { logEntry["failure_reason"] = failureReason } if let underlying = nsError.userInfo[NSUnderlyingErrorKey] as? NSError { logEntry["underlying"] = [ "domain": underlying.domain, "code": underlying.code, "description": underlying.localizedDescription ] } if let multiple = nsError.userInfo[NSMultipleUnderlyingErrorsKey] as? [NSError] { logEntry["underlying_multiple"] = multiple.map { ["domain": $0.domain, "code": $0.code, "description": $0.localizedDescription] } } if let userMessage = nsError.userInfo["ASAuthorizationErrorUserVisibleMessageKey"] as? String { logEntry["user_visible_message"] = userMessage } analytics.log("passkey_error", properties: logEntry) }
The robust fields (domain, code, localizedDescription, localizedFailureReason, NSUnderlyingErrorKey) are public API and stable across iOS versions. The ASAuthorizationErrorUserVisibleMessageKey is a framework-specific userInfo key - log it when present but do not depend on it for user-facing messages. Instead, map known ASAuthorizationError.Code values to your own strings for display and use the raw underlying error details for debugging.
| Robust (public, stable) | Best effort (may change) |
|---|---|
domain, code, localizedDescription, localizedFailureReason | Framework-specific userInfo keys like "ASAuthorizationErrorUserVisibleMessageKey" |
userInfo[NSUnderlyingErrorKey] when present | Multiple underlying errors via NSMultipleUnderlyingErrorsKey |
Mapping ASAuthorizationError.Code from the integer code | Distinguishing some passkey outcomes that Apple intentionally collapses |
| Severity | Codes | Action |
|---|---|---|
| Expected | 1001 (user cancel), 1005 (no credentials with preferImmediatelyAvailable), 1006 (excluded credential) | Normal flow outcomes. No action needed |
| Environmental | 1010 (device not configured), ASC code 5 (no credentials), LAError -5 (no passcode) | Not fixable by your code. Suppress passkey prompts for these devices via passkey intelligence |
| Transient | LAError -4 (system cancel), LAError -8 (biometric lockout) | Retry once or offer passcode fallback |
| Configuration | 1003 (not handled), "iCloud Keychain is off", "TCC access denied" | Fix your entitlements, AASA configuration or guide user to enable iCloud Keychain |
| Platform | 1000 (unknown), 1002 (invalid response) | Log and monitor. Likely Apple framework bugs |
For the broader native reliability picture including Android vs iOS abort rate analysis, see Native App Passkey Errors.
The key takeaway from Apple's passkey error stack is that Apple is progressively solving the information-loss problem at the API level. Each iOS release extracts specific failure modes from the generic code 1004 into dedicated codes: 1005 for no credentials (iOS 15), 1006 for duplicate credentials (iOS 18), 1010 for device not configured (iOS 26). On modern iOS, the most common passkey failures all have clean, dedicated signals that require no string matching or error unwrapping.
For the Android error code reference, see GPS Passkey Error Codes. For the web browser error perspective, see WebAuthn Errors in Production. For setting up your analytics framework, see Passkey Analytics.
Igor Gjorgjioski
Head of Digital Channels & Platform Enablement, VicRoads
Corbado proved to be a trusted partner. Their hands-on, 24/7 support and on-site assistance enabled a seamless integration into VicRoads' complex systems, offering passkeys to 5 million users.
Passkeys that millions adopt, fast. Start with Corbado's Adoption Platform.
Start Free TrialRelated Articles
Table of Contents