Get your free and exclusive +30-page Authentication Analytics Whitepaper

Apple Passkey Error Codes: Complete Reference (2026)

Complete reference of Apple's passkey error codes across macOS 26.2 and iOS 26.2. Maps ASCAuthorizationError to ASAuthorizationError to DOMException.

Vincent Delitz

Vincent

Created: March 5, 2026

Updated: March 5, 2026

Blog-Post-Header-Image

1. Introduction#

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.

StateOfPasskeys Icon

Want to find out how many people use passkeys?

View Adoption Data

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:

2. Architecture: how Apple generates and routes Errors#

Every 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.

LayerComponentError domainWhat happens
1AuthenticationServicesAgent (XPC daemon)ASCAuthorizationErrorDomain (codes 0-22)Generates specific errors with user-visible messages like "Cannot create a passkey. iCloud Keychain is off."
2ASAuthorizationController (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
3aSafari/WebKitDOMExceptionTranslates ASAuthorizationError to DOMExceptions. Only three codes get specific handling: 1001 → NotAllowedError, 1006 → InvalidStateError, 1004 + security error → SecurityError. Everything else becomes generic NotAllowedError
3bNative appsASAuthorizationErrorDomain (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
The critical insight: on iOS 18+ and iOS 26, most common passkey failures have dedicated public codes (1005, 1006, 1010) that need no unwrapping. When you do receive 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.
Debugger Icon

Want to experiment with passkey flows? Try our Passkeys Debugger.

Try for Free

3. ASAuthorizationError (public API)#

The public error domain that native apps and Safari see. 11 codes, stable across iOS versions with new additions.

CodeNameSinceWhat it means
1000UnknowniOS 13Unknown error. Should not appear in production
1001CancelediOS 13User dismissed the passkey sheet. The most common error. On iOS, this is a clean signal unlike Android where user cancel is harder to distinguish
1002InvalidResponseiOS 13The authorization response was invalid. Rare - indicates framework-level corruption
1003NotHandlediOS 13The authorization request was not handled by any provider. Check your entitlements and configuration
1004FailediOS 13Generic 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)
1005NotInteractiveiOS 15UI is required but the request was made with preferImmediatelyAvailableCredentials. This is the "no credential available" signal - the iOS equivalent of a clean "not found"
1006MatchedExcludedCredentialiOS 18A credential in the excludeCredentials list already exists on this device. Clean signal for duplicate detection
1007CredentialImportiOS 18.2Credential import operation failed
1008CredentialExportiOS 18.2Credential export operation failed
1009PreferSignInWithAppleiOS 26User prefers to use Sign in with Apple instead of a passkey
1010deviceNotConfiguredForPasskeyCreationiOS 26New in iOS 26. Device lacks passcode or iCloud Keychain configuration required for passkey creation. Previously this was hidden inside code 1004

3.1 iOS Version Evolution: how Apple is emptying the 1004 Bucket#

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 versionWhat was extracted from 1004New dedicated codeImpact
iOS 15No credentials (preferImmediatelyAvailableCredentials)1005No longer need to string-match "no credentials" on 1004
iOS 18Duplicate credential (excludeCredentials match)1006No longer need WKErrorDomain code 8 fallback or string matching
iOS 26Device not configured (no passcode, iCloud Keychain off)1010No 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.

3.2 NSError userInfo Keys#

When you receive an ASAuthorizationError, several userInfo keys carry diagnostic information:

KeyStatusWhat it contains
NSUnderlyingErrorKeyPublic APIThe 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
NSMultipleUnderlyingErrorsKeyPublic APIArray of underlying errors - some ASAuthorization failures use this instead of the single-error key
NSLocalizedFailureReasonErrorKeyPublic APILocalized 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
ASAuthorizationErrorUserVisibleMessageKeyFramework-specificHuman-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
ASExtensionLocalizedFailureReasonErrorKeyFramework-specificLocalized 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:

  • Firebase iOS Auth docs explicitly state: "When investigating or logging errors, review the userInfo dictionary. NSUnderlyingErrorKey contains the underlying error that caused the error in question."
  • Atomic iOS SDK demonstrates casting to NSError, reading userInfo[NSUnderlyingErrorKey] and handling @unknown default across every error callback
  • RevenueCat's error utilities wrap backend and StoreKit errors into public error codes with the original preserved in NSUnderlyingErrorKey
  • The same domain/code matching pattern applies to Foundation errors like FileManager where the surface error hides the actual POSIX cause

In 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.

4. ASCAuthorizationError (private framework)#

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.

CodeUser-visible messageCategoryWhat it means
0(no description)UnknownUnknown error
1(no description)General failureGeneric failure
2(no description)User canceledUser canceled at the daemon layer
4"Too many NFC devices found. Please present only one NFC device."NFC / security keyMultiple NFC security keys detected simultaneously
5"Found no credentials on this device."No credentialsNo 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 failureGeneric operation failure
7(biometry-dependent, see below)Duplicate credentialA passkey already exists for this account
8"Unrecognized PIN code. Please try again."Security key PINIncorrect PIN entered for a hardware security key
9"Your security key is temporarily locked. Please remove it and try again."Security key lockedToo many recent PIN failures, temporary lockout
10"Too many invalid PIN attempts. Your security key must be reset."Security key PIN lockoutPermanent PIN lockout requiring security key reset
11(returns nil)Unused/reservedNo description assigned
12(returns nil)Cancel variantMapped to ASAuthorizationErrorCanceled (1001), no message needed
13"Your security key's storage is full."Security key fullThe FIDO2 security key has no remaining credential slots
14(no description)Invalid responseInvalid response from the authenticator
17(no description)Device not configuredDevice not configured for passkey creation (maps to 1010 on iOS 26+)
20"Pin entered was too short. Please choose a longer one."Security key PINPIN length validation failure (too short)
21"Pin entered was too long. Please choose a shorter one."Security key PINPIN length validation failure (too long)
22(no description)Device not configuredSecond device-not-configured variant (also maps to 1010)

4.1 Code 7 - biometry-dependent Messages#

Code 7 has three distinct code paths based on runtime device state:

ConditionMessage
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."
WhitepaperAuthenticationAnalytics Icon

Authentication Analytics Whitepaper:
Track passkey adoption & impact on revenue.

Get Whitepaper

5. Error Code Translation Mapping#

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 CodePublic CodePublic NameInformation lost?
01000UnknownNo
11004FailedYes - generic wrapper
21001CanceledNo
121001CanceledNo
141002InvalidResponseNo
171010DeviceNotConfiguredForPasskeyNo (iOS 26+)
221010DeviceNotConfiguredForPasskeyNo (iOS 26+)
4-11, 13, 20-21nil→ 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

6. What Websites see vs what RP native Apps see#

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.

ASAuthorizationErrorCodeDOMExceptionNotes
Canceled1001NotAllowedErrorUser dismissed the sheet. Cannot be distinguished from other NotAllowedErrors in JavaScript
MatchedExcludedCredential1006InvalidStateErrorClean signal for excludeCredentials
Failed + ASCAuthorizationErrorSecurityError underlying1004SecurityErrorRP ID / origin mismatch
Failed + other underlying1004NotAllowedErrorGeneric fallback
Unknown1000ASSERT_NOT_REACHEDShould never happen
InvalidResponse1002ASSERT_NOT_REACHEDShould never happen
NotHandled1003ASSERT_NOT_REACHEDShould never happen
NotInteractive1005ASSERT_NOT_REACHEDShould never happen
DeviceNotConfiguredForPasskey1010(not handled)Falls through - new code not yet in WebKit translation
Substack Icon

Subscribe to our Passkeys Substack for the latest news.

Subscribe

7. Common Pitfalls in Passkey Error Handling#

Based on analysis of production passkey SDKs, these are the most common mistakes in Apple passkey error handling:

  • String matching on 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.
  • Mapping unknown codes to "cancelled." Some SDKs have 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.
  • Not handling codes added in newer iOS versions. If your switch statement doesn't have cases for 1005, 1006, 1009, 1010, those codes fall into your default case. Add explicit cases for all known codes and use a safe default for future codes.
  • Using plain 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) } }

8. LAError (biometric / LocalAuthentication)#

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.

CodeNameSinceWhat it means
-1AuthenticationFailediOS 8User failed biometric or passcode verification
-2UserCanceliOS 8User tapped Cancel on the biometric prompt
-3UserFallbackiOS 8User tapped "Enter Password" to use passcode instead of biometric
-4SystemCanceliOS 8System canceled (app went to background, incoming call)
-5PasscodeNotSetiOS 8No passcode configured. Passkeys require this. Related to ASCAuthorizationError 17/22
-6BiometryNotAvailableiOS 11Biometric hardware not available on this device
-7BiometryNotEnrollediOS 11No Face ID or Touch ID enrolled. Passkeys still work with passcode fallback
-8BiometryLockoutiOS 11Too many failed biometric attempts. Device requires passcode to unlock biometry
-9AppCanceliOS 9Your app called invalidate() during authentication
-10InvalidContextiOS 9The LAContext was previously invalidated
-11BiometryNotPairedmacOS 11.2Removable biometric accessory not paired (macOS only)
-12BiometryDisconnectedmacOS 11.2Removable biometric accessory disconnected (macOS only)
-14CompanionNotAvailableiOS 18No paired companion device (e.g. Apple Watch) nearby for authentication
-1004NotInteractiveiOS 8UI 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).

9. Additional error strings#

Beyond the structured error codes, the Apple frameworks contain dozens of diagnostic strings that appear in logs, crash reports and error userInfo dictionaries.

9.1 iCloud Keychain and Configuration#

StringContext
"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

9.2 Access Control and Entitlements#

StringContext
"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
Enterprise Icon

Get free passkey whitepaper for enterprises.

Get for free

10. Practical implications for error handling#

10.1 What to log in Production#

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, localizedFailureReasonFramework-specific userInfo keys like "ASAuthorizationErrorUserVisibleMessageKey"
userInfo[NSUnderlyingErrorKey] when presentMultiple underlying errors via NSMultipleUnderlyingErrorsKey
Mapping ASAuthorizationError.Code from the integer codeDistinguishing some passkey outcomes that Apple intentionally collapses

10.2 Error Severity Classification#

SeverityCodesAction
Expected1001 (user cancel), 1005 (no credentials with preferImmediatelyAvailable), 1006 (excluded credential)Normal flow outcomes. No action needed
Environmental1010 (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
TransientLAError -4 (system cancel), LAError -8 (biometric lockout)Retry once or offer passcode fallback
Configuration1003 (not handled), "iCloud Keychain is off", "TCC access denied"Fix your entitlements, AASA configuration or guide user to enable iCloud Keychain
Platform1000 (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.

11. Conclusion#

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 Testimonial

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 Trial

Add passkeys to your app in <1 hour with our UI components, SDKs & guides.

Start Free Trial

Share this article


LinkedInTwitterFacebook