Skip to content

improve error handling for excludeCredentials on iOS 26 and Android 16+#237

Open
acn-masatadakurihara wants to merge 3 commits intocorbado:mainfrom
acn-masatadakurihara:fix/exclude-credentials-error-mapping
Open

improve error handling for excludeCredentials on iOS 26 and Android 16+#237
acn-masatadakurihara wants to merge 3 commits intocorbado:mainfrom
acn-masatadakurihara:fix/exclude-credentials-error-mapping

Conversation

@acn-masatadakurihara
Copy link
Copy Markdown

Problem

Resolves #232

On iOS 26 and Android 16+, calling register() to re-register an existing passkey throws an unmapped PlatformException instead of the expected ExcludeCredentialsCanNotBeRegisteredException. This forces consumer applications to implement fragile, platform-specific error string parsing.

Root Cause

The issue stems from inconsistent error handling across three layers:

  1. iOS (ErrorExtension.swift)

    • ASAuthorizationError code 1006 is undocumented and not mapped
    • Falls through to generic "unknown" code, preventing proper exception classification
    • Only WKErrorDomain is currently handled
  2. Android (MessageHandler.java)

    • Relies on exact string equality for error message detection
    • Vulnerable to message format variations across Android OS versions and Google Play Services updates
    • Existing TODO comment acknowledges the need for refactoring
  3. Dart (authenticator.dart)

    • register() method lacks unhandled exception wrapping
    • Unlike authenticate(), it doesn't convert platform errors to Dart exception codes
    • Raw PlatformException bubbles up to consumers

Solution

Commit 1: iOS Native Map

  • Add mapping: ASAuthorizationError code 1006 → "exclude-credentials-match"
  • Enables Dart layer to properly classify the error

Commit 2: Android Native Map

  • Replace exact string match with case-insensitive substring matching
  • Resilient to message format variations across OS/GPS versions

Commit 3: Dart Layer Unification

  • Add unhandled error wrapping to register() matching authenticate()
  • Ensures ios-unhandled and android-unhandled codes are wrapped as UnhandledAuthenticatorException

Impact

No breaking changes — all changes are additive exception mappings
Consumer benefit — can now catch ExcludeCredentialsCanNotBeRegisteredException consistently
API consistencyregister() and authenticate() error handling now aligned

Testing

  • Verified on iOS 26 and Android 16+ for re-registration scenarios
  • Backward compatible with existing exception handling in authenticate()

References

…redentials-match

ASAuthorizationError code 1006 (.matchedExcludedCredential, iOS 18+)
indicates the device already holds a credential listed in
excludeCredentials. Previously this fell through to the default case
and was mapped to "unknown", causing a raw PlatformException to
surface instead of ExcludeCredentialsCanNotBeRegisteredException.

Closes corbado#232
…rror detection

Use case-insensitive substring match (contains) instead of exact
string comparison (Objects.equals) for detecting excluded credentials
errors. The error message can vary across Android / Google Play
Services versions and the previous exact match missed cases where
the error arrives as TYPE_INVALID_STATE_ERROR with a different
message format.

Closes corbado#232
…nhandledAuthenticatorException

The register() method's default PlatformException handler used
rethrow, leaking raw PlatformExceptions to callers. The authenticate()
method already wraps android-unhandled and ios-unhandled codes in
UnhandledAuthenticatorException. Apply the same pattern to register()
for consistency.

Also applies dart format to the file.

Closes corbado#232
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[iOS/Android] Duplicate passkey registration not mapped to ExcludeCredentialsCanNotBeRegisteredException

1 participant