Skip to content

Conversation

@peterferguson
Copy link
Owner

@peterferguson peterferguson commented Nov 5, 2025

Summary

Fixes #45 - excludeCredentials does not return expected InvalidStateError message when credential already exists

This PR enhances iOS error handling to provide clear, descriptive error messages instead of generic "(null)" or "The operation couldn't be completed" messages.

Changes

  • Enhanced error detection: Added logic to detect generic/useless error messages from iOS
  • WebAuthn compliance: Maps ASAuthorizationError codes to proper WebAuthn error names (NotAllowedError, EncodingError, NotSupportedError, OperationError, InvalidStateError)
  • Descriptive messages: Provides clear error messages for all error cases, including the new iOS 18.0+ ASAuthorizationErrorMatchedExcludedCredential (error code 1006)
  • Test support: Added example app test button to verify excludeCredentials error handling

Files Changed

  • ios/PasskeyExceptions.swift: Added new exception classes for different error types
  • ios/ReactNativePasskeysModule.swift: Enhanced handleASAuthorizationError function with generic error detection and proper error mapping
  • example/src/app/index.tsx: Added test button to demonstrate excludeCredentials error handling

Testing

Run the example app and use the new "Test excludeCredentials" button to verify that attempting to create a credential that's already registered returns a proper InvalidStateError with a descriptive message.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Improved iOS error handling for WebAuthn operations with better detection and mapping of native errors.
    • Enhanced error messages for excludeCredentials scenarios and other WebAuthn edge cases, providing more descriptive feedback to users on iOS.

@coderabbitai
Copy link

coderabbitai bot commented Nov 5, 2025

Walkthrough

iOS error handling for excludeCredentials is improved by detecting generic iOS error messages and mapping ASAuthorizationError codes to appropriate WebAuthn error names. A test function is added to the example app, and new exception classes are introduced to support descriptive error messages across all failure cases.

Changes

Cohort / File(s) Summary
Changelog entry
.changeset/fix-invalid-state-error-45.md
Patch release documenting improved iOS error handling for excludeCredentials with generic error message detection and WebAuthn error code mapping.
iOS error handling
ios/PasskeyExceptions.swift, ios/ReactNativePasskeysModule.swift
Added helper to detect generic iOS error messages; enhanced error mappings for six ASAuthorizationError codes with conditional WebAuthn-equivalent messages; introduced four new exception classes (MatchedExcludedCredentialException, InvalidResponseException, NotHandledException, NotInteractiveException) to propagate descriptive messages without hard-coded overrides.
Example test
example/src/app/index.tsx
Added testExcludeCredentials function and UI button to validate excludeCredentials flow by attempting passkey creation with an existing credential ID and expecting InvalidStateError.

Sequence Diagram

sequenceDiagram
    participant User
    participant App as Example App
    participant Module as ReactNativePasskeysModule
    participant iOS as iOS ASAuthorizationController
    participant Exceptions as Exception Classes

    User->>App: Tap "Test excludeCredentials"
    App->>Module: Call createPasskey with excludeCredentials
    Module->>iOS: Request passkey creation
    iOS-->>Module: Return ASAuthorizationError (e.g., code 1006)
    
    rect rgb(200, 220, 255)
    Note over Module: Error Detection & Mapping
    Module->>Module: Call isGenericErrorMessage()
    Module->>Module: Map error code to WebAuthn equivalent
    Module->>Exceptions: Create mapped exception<br/>(e.g., InvalidStateError)
    end
    
    Exceptions-->>Module: Return exception with descriptive message
    Module-->>App: Reject with mapped error
    App-->>User: Display error (e.g., "credential already exists")
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • ios/ReactNativePasskeysModule.swift: Requires careful validation of the error code mappings (1000–1006) to ensure each maps to the correct WebAuthn error and the conditional logic for generic vs. localized messages is sound.
  • ios/PasskeyExceptions.swift: Verify that removing explicit reason overrides allows proper error message propagation through the initializer.
  • example/src/app/index.tsx: Confirm the test function correctly exercises the excludeCredentials flow and surfaces the mapped error to the user.

Suggested reviewers

  • renanmav

Poem

🐰 Hops of joy for errors clear,
No more generic messages to fear!
iOS now maps with WebAuthn's grace,
excludeCredentials finds its rightful place!

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: fixing iOS error handling for excludeCredentials and linking it to issue #45, which is the primary objective of this PR.
Linked Issues check ✅ Passed All coding requirements from issue #45 are met: detects generic iOS error messages, maps ASAuthorizationError codes to WebAuthn errors including InvalidStateError for matched excluded credentials, and returns descriptive error messages.
Out of Scope Changes check ✅ Passed All changes are directly in scope: new exception classes, enhanced error mapping in ReactNativePasskeysModule, and a test button in the example app to verify excludeCredentials error handling as specified in issue #45.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/invalid-state-error-45

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12f498a and b91b357.

📒 Files selected for processing (4)
  • .changeset/fix-invalid-state-error-45.md (1 hunks)
  • example/src/app/index.tsx (2 hunks)
  • ios/PasskeyExceptions.swift (2 hunks)
  • ios/ReactNativePasskeysModule.swift (1 hunks)

Comment on lines +227 to +235
const json = await passkey.create({
...createOptions,
excludeCredentials: [{ id: credentialId, type: "public-key" }],
});

console.log("excludeCredentials test result -", json);
alert("Unexpected Success", "This should have failed with InvalidStateError");
setResult(json);
} catch (e) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix undefined createOptions reference before shipping.

testExcludeCredentials spreads createOptions, but nothing with that name is defined in this component. Hitting the new button will immediately throw a ReferenceError instead of reproducing the iOS error flow, so the demo can’t validate the fix. Bring the full creation payload inline (or share it from createPasskey) before calling passkey.create.

-			const json = await passkey.create({
-				...createOptions,
-				excludeCredentials: [{ id: credentialId, type: "public-key" }],
-			});
+			const json = await passkey.create({
+				challenge,
+				pubKeyCredParams: [{ alg: -7, type: "public-key" }],
+				rp,
+				user,
+				authenticatorSelection,
+				extensions: {
+					...(Platform.OS !== "android" && { largeBlob: { support: "required" } }),
+					prf: {},
+				},
+				excludeCredentials: [{ id: credentialId, type: "public-key" }],
+			});

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In example/src/app/index.tsx around lines 227 to 235, the function
testExcludeCredentials references createOptions which is undefined causing a
ReferenceError; replace the spread with the actual creation payload (either
inline the full create options object used elsewhere or import/share the
createPasskey payload) so passkey.create receives a complete, defined options
object including challenge, rp, user, pubKeyCredParams, timeout, and any other
required fields; ensure excludeCredentials is merged into that defined object
before calling passkey.create.

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.

excludeCredentials does not return expected InvalidStateError message when credential already exists

2 participants