Skip to content

feat: Passkey enrollment support#857

Merged
NandanPrabhu merged 14 commits into
feat/passkeys_apisfrom
feat/passkey_enrollment_myaccount_api
Jun 11, 2026
Merged

feat: Passkey enrollment support#857
NandanPrabhu merged 14 commits into
feat/passkeys_apisfrom
feat/passkey_enrollment_myaccount_api

Conversation

@NandanPrabhu

@NandanPrabhu NandanPrabhu commented Jun 9, 2026

Copy link
Copy Markdown
Contributor
  • All new/changed/fixed functionality is covered by tests (or N/A)
  • I have added documentation for all new/changed functionality (or N/A)

📋 Changes

Adds passkey enrollment to the My Account API for iOS (16.6+) and Android (API 28+), letting a signed-in user register a new passkey as an MFA authentication method. This extends the existing MyAccountApi (introduced in #835) with the two-step WebAuthn registration flow, following Auth0's My Account API.

The SDK requests the enrollment challenge and submits the resulting credential; presenting the OS passkey-creation UI is left to the integrating app (the SDK is intentionally free of any authenticator/UI dependency), consistent with the existing passkey login/signup flows.

New public API (auth0.myAccount(accessToken: ...)MyAccountApi):

  • enrollPasskeyChallenge({String? userIdentityId, String? connection})PasskeyEnrollmentChallenge — requests a WebAuthn registration challenge.
  • enrollPasskey({required PasskeyEnrollmentChallenge challenge, required PasskeyCredential credential})MyAccountPasskeyAuthenticationMethod — submits the app-supplied attestation to complete enrollment.

Both require a My Account access token (audience https://{domain}/me/) with the create:me:authentication_methods scope.

New types (exported from auth0_flutter and auth0_flutter_platform_interface):

  • PasskeyEnrollmentChallengeauthenticationMethodId, authSession, authParamsPublicKey (WebAuthn creation options).
  • MyAccountPasskeyAuthenticationMethod — the enrolled method (id, type, keyId, publicKey, userHandle, credentialDeviceType, credentialBackedUp, aaguid, relyingPartyId, transports, createdAt, …).
  • Request-option classes MyAccountEnrollPasskeyChallengeOptions and MyAccountEnrollPasskeyOptions.
  • Reuses the existing unified PasskeyCredential / PasskeyAuthenticatorResponse types from the passkey login/signup work.

Platform interface / method channel:

  • Auth0FlutterMyAccountPlatform gains enrollPasskeyChallenge(...) and enrollPasskey(...).
  • New channel methods myAccount#enrollPasskeyChallenge and myAccount#enrollPasskey on the auth0.com/auth0_flutter/my_account channel.

Native implementation:

  • iOS/macOS (darwin/): MyAccountEnrollPasskeyChallengeMethodHandler and MyAccountEnrollPasskeyMethodHandler (registered in MyAccountHandler, with MyAccountExtensions mapping helpers). Mirrored into ios/ and macos/ via the source symlinks.
  • Android: EnrollPasskeyChallengeRequestHandler and EnrollPasskeyRequestHandler (registered in Auth0FlutterPlugin, with MyAccountExtensions mapping helpers).

📎 References

Note: Passkey enrollment via the My Account API is currently in Early Access and must be enabled for your tenant. It also requires Multi-Resource Refresh Tokens (to obtain a me/-scoped token) and the relying-party domain to be configured for associated domains (iOS/macOS) / Digital Asset Links (Android).

🎯 Testing

Automated:

  • Dart: method_channel_auth0_flutter_my_account_test.dart covers both new channel methods (argument mapping + result parsing for PasskeyEnrollmentChallenge and MyAccountPasskeyAuthenticationMethod).
  • iOS/macOS: MyAccountEnrollPasskeyChallengeMethodHandlerTests, MyAccountEnrollPasskeyMethodHandlerTests (with MyAccountSpies).
  • Android: EnrollPasskeyChallengeRequestHandlerTest, EnrollPasskeyRequestHandlerTest.

Manual (end-to-end on physical devices):

  1. Run the example app and tap My Account API.
  2. Sign in for My Account API — Web Auth with audience https://{domain}/me/ and the me: scopes.
  3. The enrolled authentication methods list loads.
  4. Tap Enroll passkey → the OS passkey-creation sheet (Face ID / fingerprint) appears → on success the new passkey appears in the list.

Verified on a physical iPhone (iOS 16.6+) and a physical Android device (API 28+).

Summary by CodeRabbit

Release Notes

  • New Features

    • Added passkey enrollment support to My Account API across Android and iOS/macOS platforms. Developers can now request enrollment challenges and complete passkey registration using a two-step flow.
  • Documentation

    • Updated example documentation with passkey enrollment guidance, including required tenant configuration and OAuth scope requirements.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (3)
  • main
  • beta-release/.*
  • release/.*

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3ac4c4cd-0c31-4cb6-abb5-936dff6314a0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR implements passkey enrollment for the Auth0 Flutter SDK, adding a complete cross-platform flow for requesting enrollment challenges and submitting credentials. The feature spans Dart models, platform interface contracts, method-channel wiring, and native Android/iOS implementations with corresponding tests and documentation.

Changes

My Account Passkey Enrollment

Layer / File(s) Summary
Data models and request options
auth0_flutter_platform_interface/lib/src/myaccount/my_account_passkey_authentication_method.dart, my_account_passkey_enrollment_challenge.dart, my_account_enroll_passkey_challenge_options.dart, my_account_enroll_passkey_options.dart
PasskeyEnrollmentChallenge holds the WebAuthn creation options returned by the API, while MyAccountPasskeyAuthenticationMethod represents an enrolled passkey. Request option classes carry parameters through the method channel.
Platform interface and method channel
auth0_flutter_platform_interface/lib/src/myaccount/auth0_flutter_my_account_platform.dart, method_channel_auth0_flutter_my_account.dart, barrel exports, test coverage
Abstract platform methods enrollPasskeyChallenge and enrollPasskey define the contract; method-channel implementations route calls and deserialize responses via fromMap factories.
Dart Mobile API
auth0_flutter/lib/src/mobile/my_account_api.dart, lib/auth0_flutter.dart
MyAccountApi exposes two public methods: enrollPasskeyChallenge (requests a WebAuthn creation challenge with optional identity/connection hints) and enrollPasskey (submits a credential and challenge for enrollment). Newly enrolled authentication methods are exported publicly.
Android request handlers and wiring
android/src/main/kotlin/com/auth0/auth0_flutter/Auth0FlutterPlugin.kt, MyAccountExtensions.kt, request_handlers/my_account/EnrollPasskeyChallengeRequestHandler.kt, EnrollPasskeyRequestHandler.kt, tests
EnrollPasskeyChallengeRequestHandler reads userIdentityId and connection, invokes the SDK method, and converts results. EnrollPasskeyRequestHandler reconstructs typed models from Flutter maps (including JSON parsing of authParamsPublicKey) and invokes SDK enrollment. Both handlers route success/failure to the MethodChannel result. Extension functions map SDK types to Flutter maps. Comprehensive unit tests verify parameter forwarding, result conversion, and error handling.
iOS/Darwin method handlers and wiring
darwin/Classes/MyAccountAPI/MyAccountEnrollPasskeyChallengeMethodHandler.swift, MyAccountEnrollPasskeyMethodHandler.swift, MyAccountExtensions.swift, MyAccountHandler.swift, symlinks at ios/Classes/ and macos/Classes/, tests, spy implementations
MyAccountEnrollPasskeyChallengeMethodHandler extracts arguments and invokes the SDK challenge request. MyAccountEnrollPasskeyMethodHandler reconstructs PasskeyEnrollmentChallenge and NewPasskey from Flutter maps (with base64url decoding and JSON parsing), invokes SDK enrollment, and converts results to dictionaries. Both are gated by PASSKEYS_PLATFORM compile flag and platform availability checks. Extension methods serialize SDK types to dictionaries with ISO8601 timestamps and nested fields. Tests verify handler behavior with spy clients; symlinks at iOS/macOS paths share Darwin implementation.
Documentation and example updates
EXAMPLES.md, example/lib/example_app.dart, example/android/app/strings.xml.example, example/ios/Runner.xcodeproj/project.pbxproj
Documentation adds a new "Enrolling a passkey" subsection with prerequisites and a Dart code example. Example app removes deprecated passkey login/signup flows and their token preview helpers, updates Web and API login handlers to expose full token strings, and removes corresponding UI buttons. Xcode project includes new test files; Android strings.xml example defines Auth0 domain and scheme placeholders.

Sequence Diagram

sequenceDiagram
    participant App as Client App
    participant MyAccountApi as MyAccountApi
    participant MethodChannel as Method Channel
    participant AndroidHandler as Android Handler
    participant iOSHandler as iOS Handler
    participant SDK as Auth0 SDK
    App->>MyAccountApi: enrollPasskeyChallenge(userIdentityId?, connection?)
    MyAccountApi->>MethodChannel: invokeMapRequest(myAccount#enrollPasskeyChallenge)
    alt Android
        MethodChannel->>AndroidHandler: handle()
        AndroidHandler->>SDK: passkeyEnrollmentChallenge(userIdentityId, connection)
        SDK-->>AndroidHandler: PasskeyEnrollmentChallenge
        AndroidHandler-->>MethodChannel: success(challenge.toMap())
    else iOS/macOS
        MethodChannel->>iOSHandler: handle()
        iOSHandler->>SDK: passkeyEnrollmentChallenge(userIdentityId, connection)
        SDK-->>iOSHandler: PasskeyEnrollmentChallenge
        iOSHandler-->>MethodChannel: callback(challenge.asDictionary())
    end
    MethodChannel-->>MyAccountApi: challenge map
    MyAccountApi-->>App: PasskeyEnrollmentChallenge
    App->>App: Present OS passkey UI
    App->>MyAccountApi: enrollPasskey(challenge, credential)
    MyAccountApi->>MethodChannel: invokeMapRequest(myAccount#enrollPasskey)
    alt Android
        MethodChannel->>AndroidHandler: handle()
        AndroidHandler->>SDK: enroll(credential, challenge)
        SDK-->>AndroidHandler: PasskeyAuthenticationMethod
        AndroidHandler-->>MethodChannel: success(method.toMap())
    else iOS/macOS
        MethodChannel->>iOSHandler: handle()
        iOSHandler->>SDK: enroll(passkey, challenge)
        SDK-->>iOSHandler: PasskeyAuthenticationMethod
        iOSHandler-->>MethodChannel: callback(method.asDictionary())
    end
    MethodChannel-->>MyAccountApi: method map
    MyAccountApi-->>App: MyAccountPasskeyAuthenticationMethod
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • auth0/auth0-flutter#835: Extends the platform-interface method routing already established in this PR for passkey enrollment by adding downstream platform-specific implementations.

Suggested reviewers

  • pmathew92
  • sanchitmehtagit

🐰 A hop, skip, and a leap through the code,
Passkeys now enroll on Android, iOS road,
From challenge to credential, the SDK knows,
Two-step enrollment flow that securely grows!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: Passkey enrollment support' accurately and concisely describes the primary change—adding passkey enrollment functionality to the My Account API.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/passkey_enrollment_myaccount_api

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.

@NandanPrabhu

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
auth0_flutter/EXAMPLES.md (1)

1600-1608: 💤 Low value

Consider including authenticatorAttachment for consistency.

The passkey enrollment example constructs a PasskeyCredential without the authenticatorAttachment field, but the login example (lines 1133-1142) includes authenticatorAttachment: 'platform'. For consistency and completeness, consider adding this field to the enrollment example or documenting that it's optional.

📝 Suggested addition
final credential = PasskeyCredential(
  id: '...',
  rawId: '...',
  type: 'public-key',
+ authenticatorAttachment: 'platform',
  response: PasskeyAuthenticatorResponse(
    clientDataJSON: '...',
    attestationObject: '...',
  ),
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth0_flutter/EXAMPLES.md` around lines 1600 - 1608, The enrollment example
for PasskeyCredential is missing the optional authenticatorAttachment property
used in the login example; update the PasskeyCredential instantiation (symbol:
PasskeyCredential) to include authenticatorAttachment: 'platform' (or document
its optionality) so the example matches the login snippet and is consistent with
PasskeyAuthenticatorResponse usage; ensure the field is added alongside id,
rawId, type and response in the example.
auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/MyAccountExtensions.kt (1)

69-81: ⚡ Quick win

Consider refactoring the double Gson serialization.

The double serialization Gson().fromJson(Gson().toJson(authParamsPublicKey), Map::class.java) is inefficient and loses type safety. If authParamsPublicKey is already a compatible map-like structure, consider direct casting or a more targeted conversion. If the Auth0 Android SDK returns a specific type, you could map its properties directly to avoid the round-trip overhead and the loose Map<*, *> typing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/MyAccountExtensions.kt`
around lines 69 - 81, The
PasskeyEnrollmentChallenge.toMyAccountPasskeyChallengeMap function currently
uses a double Gson serialize/deserialize round-trip
(Gson().fromJson(Gson().toJson(authParamsPublicKey), Map::class.java)) which is
inefficient and loses type information; replace this with a direct conversion by
inspecting the runtime type of authParamsPublicKey (e.g., if it's already a Map
or JSONObject) and cast it safely (or map its known properties into a new Kotlin
Map) so you avoid the Gson round-trip and use a typed Map<String, Any?> for
"authParamsPublicKey" when returning the buildMap; update any necessary null
checks/casts to preserve safety.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/my_account/EnrollPasskeyRequestHandler.kt`:
- Around line 96-106: The Response construction in EnrollPasskeyRequestHandler
(fields attestationObject, authenticatorData, signature, userHandle) currently
falls back to empty strings; instead remove those defensive defaults so missing
required fields fail the cast/parse or add explicit validation that throws a
clear exception when any of Response.attestationObject,
Response.authenticatorData, Response.signature, or Response.userHandle is
null/blank; update the code around the Response(...) creation to either use
non-null casts (to let Kotlin throw) or validate and throw a descriptive
IllegalArgumentException identifying the missing field(s) so callers receive a
clear error rather than silent empty values.

---

Nitpick comments:
In
`@auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/MyAccountExtensions.kt`:
- Around line 69-81: The
PasskeyEnrollmentChallenge.toMyAccountPasskeyChallengeMap function currently
uses a double Gson serialize/deserialize round-trip
(Gson().fromJson(Gson().toJson(authParamsPublicKey), Map::class.java)) which is
inefficient and loses type information; replace this with a direct conversion by
inspecting the runtime type of authParamsPublicKey (e.g., if it's already a Map
or JSONObject) and cast it safely (or map its known properties into a new Kotlin
Map) so you avoid the Gson round-trip and use a typed Map<String, Any?> for
"authParamsPublicKey" when returning the buildMap; update any necessary null
checks/casts to preserve safety.

In `@auth0_flutter/EXAMPLES.md`:
- Around line 1600-1608: The enrollment example for PasskeyCredential is missing
the optional authenticatorAttachment property used in the login example; update
the PasskeyCredential instantiation (symbol: PasskeyCredential) to include
authenticatorAttachment: 'platform' (or document its optionality) so the example
matches the login snippet and is consistent with PasskeyAuthenticatorResponse
usage; ensure the field is added alongside id, rawId, type and response in the
example.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ca081d1-8e90-4982-9ba3-2a08f18f1d3a

📥 Commits

Reviewing files that changed from the base of the PR and between b9e393b and 0ca4fa9.

📒 Files selected for processing (31)
  • auth0_flutter/EXAMPLES.md
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/Auth0FlutterPlugin.kt
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/MyAccountExtensions.kt
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/my_account/EnrollPasskeyChallengeRequestHandler.kt
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/my_account/EnrollPasskeyRequestHandler.kt
  • auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/my_account/EnrollPasskeyChallengeRequestHandlerTest.kt
  • auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/my_account/EnrollPasskeyRequestHandlerTest.kt
  • auth0_flutter/darwin/Classes/MyAccountAPI/MyAccountEnrollPasskeyChallengeMethodHandler.swift
  • auth0_flutter/darwin/Classes/MyAccountAPI/MyAccountEnrollPasskeyMethodHandler.swift
  • auth0_flutter/darwin/Classes/MyAccountAPI/MyAccountExtensions.swift
  • auth0_flutter/darwin/Classes/MyAccountAPI/MyAccountHandler.swift
  • auth0_flutter/example/android/app/strings.xml.example
  • auth0_flutter/example/ios/Runner.xcodeproj/project.pbxproj
  • auth0_flutter/example/ios/Tests/MyAccount/MyAccountEnrollPasskeyChallengeMethodHandlerTests.swift
  • auth0_flutter/example/ios/Tests/MyAccount/MyAccountEnrollPasskeyMethodHandlerTests.swift
  • auth0_flutter/example/ios/Tests/MyAccount/MyAccountSpies.swift
  • auth0_flutter/example/lib/example_app.dart
  • auth0_flutter/ios/Classes/MyAccountAPI/MyAccountEnrollPasskeyChallengeMethodHandler.swift
  • auth0_flutter/ios/Classes/MyAccountAPI/MyAccountEnrollPasskeyMethodHandler.swift
  • auth0_flutter/lib/auth0_flutter.dart
  • auth0_flutter/lib/src/mobile/my_account_api.dart
  • auth0_flutter/macos/Classes/MyAccountAPI/MyAccountEnrollPasskeyChallengeMethodHandler.swift
  • auth0_flutter/macos/Classes/MyAccountAPI/MyAccountEnrollPasskeyMethodHandler.swift
  • auth0_flutter_platform_interface/lib/auth0_flutter_platform_interface.dart
  • auth0_flutter_platform_interface/lib/src/myaccount/auth0_flutter_my_account_platform.dart
  • auth0_flutter_platform_interface/lib/src/myaccount/method_channel_auth0_flutter_my_account.dart
  • auth0_flutter_platform_interface/lib/src/myaccount/my_account_enroll_passkey_challenge_options.dart
  • auth0_flutter_platform_interface/lib/src/myaccount/my_account_enroll_passkey_options.dart
  • auth0_flutter_platform_interface/lib/src/myaccount/my_account_passkey_authentication_method.dart
  • auth0_flutter_platform_interface/lib/src/myaccount/my_account_passkey_enrollment_challenge.dart
  • auth0_flutter_platform_interface/test/method_channel_auth0_flutter_my_account_test.dart

@NandanPrabhu NandanPrabhu force-pushed the feat/passkey_enrollment_myaccount_api branch 2 times, most recently from e87b7a3 to 7e4ece3 Compare June 9, 2026 10:18
Comment thread auth0_flutter/darwin/Classes/MyAccountAPI/MyAccountExtensions.swift
Comment thread auth0_flutter/example/lib/example_app.dart
Base automatically changed from feat/passkeys_api_signup to feat/passkeys_apis June 9, 2026 12:31
Comment thread auth0_flutter/EXAMPLES.md Outdated
@NandanPrabhu NandanPrabhu merged commit d1b7bc5 into feat/passkeys_apis Jun 11, 2026
13 checks passed
@NandanPrabhu NandanPrabhu deleted the feat/passkey_enrollment_myaccount_api branch June 11, 2026 05:44
NandanPrabhu added a commit that referenced this pull request Jun 12, 2026
NandanPrabhu added a commit that referenced this pull request Jun 12, 2026
NandanPrabhu added a commit that referenced this pull request Jun 12, 2026
NandanPrabhu added a commit that referenced this pull request Jun 12, 2026
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.

4 participants