Skip to content

feat: Add Multi-Factor Authentication (MFA) support for mobile#872

Open
utkrishtsahu wants to merge 9 commits into
mainfrom
feat/mfa-mobile-support
Open

feat: Add Multi-Factor Authentication (MFA) support for mobile#872
utkrishtsahu wants to merge 9 commits into
mainfrom
feat/mfa-mobile-support

Conversation

@utkrishtsahu

@utkrishtsahu utkrishtsahu commented Jun 19, 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

This PR adds Multi-Factor Authentication (MFA) support to the Flutter SDK for iOS and Android, built on the flexible-grant MFA APIs in Auth0.swift and Auth0.Android. It lets apps complete MFA flows after receiving an mfa_required error during authentication — enrolling new factors, challenging existing ones, and exchanging a verification code for credentials.

New public API — auth0.mfa (MfaApi):

Method Description
getAuthenticators(...) Lists the user's enrolled authenticators
enrollTotp(...) Enrolls a TOTP (authenticator app) factor; returns secret + barcode URI for QR rendering
enrollPhone(...) Enrolls an SMS/Voice (OOB) factor for an E.164 phone number
enrollEmail(...) Enrolls an Email (OOB) factor
enrollPush(...) Enrolls a Push (Guardian) factor; returns a barcode URI to scan
challenge(...) Requests a challenge against an enrolled authenticator
verify(...) Verifies an otp, oob, or recoveryCode grant and returns Credentials

All methods take the mfaToken from a mfa_required authentication error.

All methods take the mfaToken from a mfa_required authentication error.

Platform interface (auth0_flutter_platform_interface):

  • New MFA method channel (auth0.com/auth0_flutter/mfa) and platform contract.
  • New models: MfaAuthenticator, MfaChallenge, MfaEnrollmentChallenge, MfaRequirements, the Mfa*Options request types, and MfaException.
  • mfa_required challenge details are now surfaced on ApiException.

Native implementations:

  • iOS/macOS: new MfaAPI/ method handlers built on the MFAClient API. Bumps Auth0.swift 2.21.2 → 2.22.0 (all three podspecs).
  • Android: new request_handlers/mfa/ request handlers on MfaApiClient. Bumps com.auth0.android:auth0 3.18.0 → 3.19.0.

Docs: MFA usage examples added to EXAMPLES.md.

Out of scope: Web and desktop platforms, and Push auto-polling, are intentionally not included.

📎 References

SDK-6406

🎯 Testing

Automated tests included:

  • Dart: unit tests for MfaApi (mfa_api_test.dart), the method channel (method_channel_auth0_flutter_mfa_test.dart), and mfa_required parsing on ApiException (api_exception_test.dart).
  • Android: Kotlin unit tests for every request handler and the MFA method-call handler (request_handlers/mfa/*Test.kt).
  • iOS: Swift unit tests for every method handler with spies (example/ios/Tests/Mfa/*).

Summary by CodeRabbit

  • New Features
    • Added end-to-end Multi-Factor Authentication (MFA) support across mobile, desktop, and web: authenticator listing, factor enrollment (TOTP/phone/email/push), challenge initiation, and verification (OTP/OOB/recovery).
    • Introduced Auth0.mfa(mfaToken) and Auth0Web.mfa(mfaToken) entry points, with strongly typed MFA options/models and structured MFA exceptions.
  • Documentation
    • Added Early Access MFA guide (mid-login mfa_token flow) and updated README with MFA links.
  • Tests
    • Added comprehensive Dart and native test coverage for MFA flows and error-to-result mapping.
  • Chores
    • Updated iOS/macOS/Auth0 dependency to 2.22.0.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds MFA support across auth0-flutter for mobile, web, Android, and Darwin. Introduces MFA contracts, platform interfaces, native method handlers, Dart entry points, tests, and updated usage docs.

Changes

MFA Feature End-to-End

Layer / File(s) Summary
MFA contracts and errors
auth0_flutter_platform_interface/lib/src/mfa/*, auth0_flutter_platform_interface/lib/src/auth/api_exception.dart
Adds MFA models, request options, verification grant typing, MFA exception handling, and ApiException parsing for MFA requirements.
Platform interface and channel bridge
auth0_flutter_platform_interface/lib/src/mfa/auth0_flutter_mfa_platform.dart, auth0_flutter_platform_interface/lib/src/mfa/method_channel_auth0_flutter_mfa.dart, auth0_flutter_platform_interface/lib/auth0_flutter_platform_interface.dart
Adds the MFA platform singleton, method-channel implementation, channel method names, and package exports.
Mobile and web MFA client entry points
auth0_flutter/lib/src/mobile/mfa_api.dart, auth0_flutter/lib/auth0_flutter.dart, auth0_flutter/lib/src/web/*, auth0_flutter/lib/auth0_flutter_web.dart, auth0_flutter/lib/src/web/js_interop.dart
Adds the mobile MfaApi, web MfaWeb, Auth0.mfa(...)/Auth0Web.mfa(...), web JS interop bindings, platform proxy methods, and web model conversion helpers.
Android MFA handlers and wiring
auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/*, auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/*
Adds Kotlin MFA serializers, request handlers, method dispatch, and plugin channel registration for Android.
Darwin MFA handlers, plugin, and podspecs
auth0_flutter/darwin/Classes/MfaAPI/*, auth0_flutter/darwin/Classes/SwiftAuth0FlutterPlugin.swift, auth0_flutter/*/*.podspec
Adds Swift MFA handlers and plugin dispatch, serializer helpers, plugin registration, and Auth0 pod version updates.
Tests and usage docs
auth0_flutter_platform_interface/test/*, auth0_flutter/test/*, auth0_flutter/EXAMPLES.md, auth0_flutter/README.md
Adds tests for MFA models, options, platform calls, handlers, and integration flows, plus MFA usage and error-handling documentation.

Sequence Diagram(s)

sequenceDiagram
  participant App as Flutter app
  participant Entry as Auth0 / Auth0Web
  participant Client as MfaApi / MfaWeb
  participant Platform as Platform interface
  participant Native as Native or web MFA runtime

  App->>Entry: mfa(mfaToken)
  Entry-->>App: MfaApi / MfaWeb
  App->>Client: getAuthenticators()
  Client->>Platform: request options
  Platform->>Native: MFA call
  Native-->>Platform: response model
  Platform-->>App: typed MFA result
  App->>Client: verifyOob / verifyOtp / verifyRecoveryCode
  Client->>Platform: verify request
  Platform->>Native: mfa verify
  Native-->>Platform: Credentials
  Platform-->>App: Credentials
</mermaid>

## Estimated code review effort
🎯 5 (Critical) | ⏱️ ~120 minutes

## Suggested reviewers
- sanchitmehtagit
- pmathew92

## Poem
> 🔐 A token whispers, “start the dance,”  
> With TOTP, push, or email chance.  
> Web and native answer in tune,  
> Credentials bloom beneath the moon.  
> MFA hums from app to API skies ✨

<!-- walkthrough_end -->
<!-- pre_merge_checks_walkthrough_start -->

<details>
<summary>🚥 Pre-merge checks | ✅ 4 | ❌ 1</summary>

### ❌ Failed checks (1 warning)

|     Check name     | Status     | Explanation                                                                          | Resolution                                                                         |
| :----------------: | :--------- | :----------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- |
| Docstring Coverage | ⚠️ Warning | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |

<details>
<summary>✅ Passed checks (4 passed)</summary>

|         Check name         | Status   | Explanation                                                                                                                                |
| :------------------------: | :------- | :----------------------------------------------------------------------------------------------------------------------------------------- |
|      Description Check     | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled.                                                                                |
|         Title check        | ✅ Passed | The title matches the main change: adding MFA support, though it narrows it to mobile while the PR also includes web and platform updates. |
|     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.                                                                   |

</details>

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

</details>

<!-- pre_merge_checks_walkthrough_end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

<details>
<summary>🧪 Generate unit tests (beta)</summary>

- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with unit tests
- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Commit unit tests in branch `feat/mfa-mobile-support`

</details>

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=auth0/auth0-flutter&utm_content=872)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>


<sub>Comment `@coderabbitai help` to get the list of available commands.</sub>

<!-- tips_end -->
Loading

@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: 22

🤖 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_platform_interface/lib/src/auth/api_exception.dart`:
- Around line 47-51: The mfaRequirements getter uses a loose is Map check that
doesn't validate the actual structure of the nested data, and
MfaRequirements.fromMap() method along with its _parseFactors() helper perform
unsafe casts without validation, causing runtime crashes on malformed payloads.
Replace the loose type guard with stricter validation that checks the actual
structure of the mfa_requirements Map before passing it to
MfaRequirements.fromMap(), and add try-catch error handling around the
MfaRequirements instantiation to gracefully handle malformed nested data (such
as 'challenge' being a string instead of the expected structure) rather than
crashing. Additionally, make the _parseFactors() method more defensive by
validating field types before casting to List instead of assuming the structure
is correct.

In
`@auth0_flutter_platform_interface/lib/src/mfa/method_channel_auth0_flutter_mfa.dart`:
- Around line 36-39: The unchecked cast `item as Map` in the `getAuthenticators`
method violates the strict-casts setting and causes malformed native payloads to
throw TypeError instead of MfaException. Guard the item variable before casting
by validating it is a Map type, and throw an appropriate MfaException if
validation fails, ensuring consistent error handling that aligns with the
project's error contract.

In `@auth0_flutter_platform_interface/lib/src/mfa/mfa_authenticator.dart`:
- Around line 38-46: The MfaAuthenticator.fromMap factory method uses unchecked
as casts directly on dynamic data from the native platform, which will cause
uncontrolled TypeErrors if the payload is malformed. Replace each unchecked as
cast (for id, type, authenticatorType, active, oobChannel, and name) with proper
type guards using is checks before casting. For each field, verify the type
using an is check, then cast only if the type matches, and provide an
appropriate default value or handle the error case appropriately to ensure
malformed native payloads are caught and handled with proper SDK exceptions
rather than raw TypeErrors.

In `@auth0_flutter_platform_interface/lib/src/mfa/mfa_challenge.dart`:
- Around line 22-27: The MfaChallenge.fromMap factory constructor uses unchecked
`as` casts for the challengeType, oobCode, and bindingMethod fields, which will
throw unexpectedly if the native layer returns incorrect types. Add type
validation using `is` guards to check each value's type before casting, and
handle cases where types don't match by raising descriptive errors that explain
the expected type and what was actually received. This ensures the Auth0 SDK
fails safely and predictably when deserializing security-critical challenge
data.

In `@auth0_flutter_platform_interface/lib/src/mfa/mfa_enrollment_challenge.dart`:
- Around line 63-76: The MfaEnrollmentChallenge.fromMap factory method uses
unchecked `as` casts on dynamic platform channel values which can throw runtime
exceptions if the native layer returns unexpected types. Guard each cast with an
`is` type check before casting, or use `whereType` for the recovery_codes list
parsing to filter elements to only Strings before converting to the list. This
prevents runtime failures when non-string values are received from the platform
channel.

In `@auth0_flutter_platform_interface/lib/src/mfa/mfa_requirements.dart`:
- Around line 8-10: The MfaFactor.fromMap factory method and related parsing
logic contain unsafe casts that violate strict-casts requirements and can crash
with invalid API data. Add type guards using `is` checks before each cast: in
MfaFactor.fromMap, verify that result['type'] is String before casting; in the
parsing logic around lines 38-40, verify that value is List<Object?>? before the
cast on line 38 and verify that e is Map before the cast on line 40. For each
guard, ensure the code handles the case where the type check fails by either
returning a default value or skipping that data to degrade gracefully instead of
crashing.

In `@auth0_flutter_platform_interface/lib/src/mfa/mfa_verify_options.dart`:
- Around line 51-68: The MfaVerifyOptions constructor allows invalid states
where grant-type-specific required fields can be null, contradicting the field
documentation and causing runtime failures on the native side. Add validation
logic in the constructor to enforce that the appropriate fields are provided
based on the grantType value. For example, when grantType is otp, the otp field
must not be null; when grantType is oob, the oobCode field must not be null. Use
conditional checks on the grantType to validate the presence of the
corresponding required field and throw an ArgumentError with a descriptive
message if validation fails.

In
`@auth0_flutter_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart`:
- Around line 18-83: The test suite is missing coverage for newly added public
MFA options and API paths. Add a test case within the options.toMap group to
verify that MfaEnrollPushOptions.toMap() correctly serializes all its fields
(similar to the existing tests for MfaEnrollPhoneOptions and
MfaEnrollEmailOptions). Additionally, add separate test cases (likely in a new
group for MethodChannel invocations) to verify that the enrollPush and
enrollEmail MethodChannel paths correctly invoke the platform channel with the
appropriate method names and pass through the options parameters correctly,
ensuring full coverage of the new public MFA contract.
- Line 201: In the test file, replace the unsafe `as Map` casts on
`log.single.arguments` at all three locations (lines 201, 224, and 254) with
proper type guards. Add an `is Map` type check before casting to `Map<String,
dynamic>`, or alternatively use `Map.from()` to safely extract and convert the
arguments before accessing dictionary keys like `['factorsAllowed']`. Apply this
fix consistently across all three occurrences to prevent runtime crashes when
strict-casts is enabled.

In
`@auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/Auth0FlutterMfaMethodCallHandler.kt`:
- Around line 21-23: The mfaToken extraction in Auth0FlutterMfaMethodCallHandler
uses an unsafe force cast that will crash if the value is not a String. Replace
the unsafe cast `as String` with a safe cast `as? String` and add a null check
to handle the case where mfaToken is not of the expected type. When the safe
cast fails and returns null, call result.error() to properly report the type
mismatch error instead of allowing a ClassCastException to propagate.

In
`@auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/MfaExtensions.kt`:
- Around line 65-71: The toMfaMap() extension function on MfaException hardcodes
the "isNetworkError" flag to false, which breaks retry semantics for transient
network errors. Instead of hardcoding false, access the actual network error
status from the MfaException instance and use that value in the errorFlags map.
Check the MfaException class definition to identify the correct property or
method that indicates whether the exception represents a network error, then
replace the hardcoded false value with that property.

In
`@auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/ChallengeRequestHandler.kt`:
- Around line 23-25: The authenticatorId extraction in the
ChallengeRequestHandler uses an unsafe force cast operator that can throw
ClassCastException if the MethodChannel payload contains an invalid type.
Replace the force cast operator used on the authenticatorId assignment with the
safe cast operator, and add appropriate null-checking to handle cases where the
cast fails. This ensures type validation errors are handled gracefully instead
of crashing at runtime.

In
`@auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollEmailRequestHandler.kt`:
- Around line 24-25: The unchecked cast `as String` on line 25 in
EnrollEmailRequestHandler can throw a ClassCastException at runtime for
malformed input, bypassing proper Flutter error propagation. Replace the force
cast with a safe cast using `as?` to validate the email type, then check if the
result is null and return `result.error(...)` with an appropriate error message
before proceeding with the email variable. This ensures invalid input is
properly surfaced through the Flutter channel rather than crashing.

In
`@auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollPhoneRequestHandler.kt`:
- Around line 24-27: The EnrollPhoneRequestHandler is dropping the type argument
sent from Dart, which specifies the phone enrollment channel (SMS or voice).
Update the assertHasProperties call to include "type" in addition to
"phoneNumber" as a required property, then extract the type value from
request.data just like phoneNumber is extracted, and finally pass this type
value as a parameter when creating the MfaEnrollmentType.Phone object in the
client.enroll call.
- Around line 24-26: In the EnrollPhoneRequestHandler class, the phoneNumber
extraction uses an unsafe cast (as String) on line 25 which can throw
ClassCastException if the data is malformed. Replace this unsafe cast with a
safe cast (as? String) and add proper null handling to validate that the value
exists and is of the correct type. Additionally, the Dart contract sends a
required type field (present in MfaEnrollPhoneOptions.toMap()) but the current
assertHasProperties call does not validate it. Modify the assertHasProperties
call to include both "phoneNumber" and "type" in the list of required fields to
ensure contract alignment and complete validation of all required fields before
processing the request.

In
`@auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/GetAuthenticatorsRequestHandler.kt`:
- Around line 21-23: The unchecked cast for factorsAllowed in
GetAuthenticatorsRequestHandler.kt uses an unsafe as? List<String> pattern that
suppresses compiler warnings but can fail at runtime when the MethodChannel
returns a list with non-String elements. Instead of relying on the suppressed
unchecked cast, replace this with explicit element validation by retrieving the
raw list from request.data["factorsAllowed"], then filtering or mapping through
the elements to validate each item is actually a String before including it in
the factorsAllowed collection. This ensures type safety and prevents
ClassCastException when list elements are accessed later.

In
`@auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/VerifyRequestHandler.kt`:
- Around line 24-42: The grantType validation logic in the when statement
contains force-casts (as String) on lines 26, 29, 34, and 40 that can throw
ClassCastException, and the entire validation block lacks error callback
handling. Wrap the entire when expression and all assertHasProperties calls in a
try-catch block, convert all force-casts to safe-casts (as? String) with
appropriate null checks, and in the catch block invoke result.error() with
descriptive error details to ensure the MethodChannel callback is invoked
exactly once regardless of validation success or failure.

In
`@auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/ChallengeRequestHandlerTest.kt`:
- Around line 41-84: The test class ChallengeRequestHandlerTest is missing
coverage for the failure path in the ChallengeRequestHandler.handle() method.
Add a new test method that sets up the same mocks (mockClient, mockRequest,
mockResult, mockAccount) as the existing success test, but instead configure the
doAnswer callback to invoke onFailure with a MfaChallengeException. Then verify
that mockResult.error() is called to ensure authentication errors are properly
propagated through the result object rather than being silently dropped. This
test ensures that the onFailure callback path in the handler is working
correctly.

In
`@auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollPhoneRequestHandlerTest.kt`:
- Around line 21-40: The test in EnrollPhoneRequestHandler only verifies that
the phoneNumber parameter is forwarded to the enroll method, but does not test
the type parameter (SMS vs voice). Add a "type" entry to the hashMap in the
MethodCallRequest constructor (with a value like "SMS" or "voice"), then update
the verify statement to ensure that both the phoneNumber and type are correctly
passed together to the enroll method call, so that regressions in the type
forwarding can be detected.

In
`@auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/VerifyRequestHandlerTest.kt`:
- Around line 26-84: Add negative test cases to validate type safety in the
VerifyRequestHandler for cases where the payload contains non-String types for
the required fields. Create additional test methods that pass incorrect types
(such as Integer, Boolean, or Map instead of String) for grantType, otp,
oobCode, and recoveryCode parameters to the handle method of
VerifyRequestHandler. Assert that these invalid inputs are properly validated
and rejected by verifying that either an appropriate error is passed to the
mockResult object or that the mockClient.verify method is not called, ensuring
the handler guards against unsafe cast crashes as required by the MethodChannel
handler guidelines.

In `@auth0_flutter/EXAMPLES.md`:
- Around line 1735-1748: The example code declares the variable credentials with
final multiple times in the same scope, which is invalid Dart syntax. To fix
this, separate the three MFA verification examples (verifyOtp, verifyOob, and
verifyRecoveryCode) into distinct code blocks or sections so each example exists
in its own scope. Alternatively, use different variable names for each
verification method (such as otpCredentials, oobCredentials, and
recoveryCredentials) if keeping them in the same code block. Ensure each example
is clearly separated and can be compiled independently without variable
redeclaration conflicts.

In `@auth0_flutter/test/mobile/mfa_api_test.dart`:
- Around line 91-103: Add test cases to cover the default parameter branches for
the MFA API. Create a new test method for enrollPhone that omits the type
parameter and verify that it defaults to PhoneType.sms by capturing and
asserting the captured request options. Additionally, add a test method for
verifyOob that omits the bindingCode parameter and verify the captured request
reflects this omission. These tests ensure both the explicit parameter paths and
the default parameter paths are covered as required by the coding guidelines for
public API symbols.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4201f9b2-ea5f-459f-88b5-6f0aff140a9e

📥 Commits

Reviewing files that changed from the base of the PR and between 3d46e0f and 20ef247.

⛔ Files ignored due to path filters (19)
  • auth0_flutter/example/ios/Runner.xcodeproj/project.pbxproj is excluded by !**/example/**
  • auth0_flutter/example/ios/Tests/Mfa/MfaChallengeMethodHandlerTests.swift is excluded by !**/example/**
  • auth0_flutter/example/ios/Tests/Mfa/MfaEnrollEmailMethodHandlerTests.swift is excluded by !**/example/**
  • auth0_flutter/example/ios/Tests/Mfa/MfaEnrollPhoneMethodHandlerTests.swift is excluded by !**/example/**
  • auth0_flutter/example/ios/Tests/Mfa/MfaEnrollPushMethodHandlerTests.swift is excluded by !**/example/**
  • auth0_flutter/example/ios/Tests/Mfa/MfaEnrollTotpMethodHandlerTests.swift is excluded by !**/example/**
  • auth0_flutter/example/ios/Tests/Mfa/MfaGetAuthenticatorsMethodHandlerTests.swift is excluded by !**/example/**
  • auth0_flutter/example/ios/Tests/Mfa/MfaSpies.swift is excluded by !**/example/**
  • auth0_flutter/example/ios/Tests/Mfa/MfaVerifyMethodHandlerTests.swift is excluded by !**/example/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaChallengeMethodHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaEnrollEmailMethodHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaEnrollPhoneMethodHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaEnrollPushMethodHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaEnrollTotpMethodHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaExtensions.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaGetAuthenticatorsMethodHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaVerifyMethodHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/test/mobile/mfa_api_test.mocks.dart is excluded by !**/*.mocks.dart
📒 Files selected for processing (46)
  • auth0_flutter/EXAMPLES.md
  • auth0_flutter/android/build.gradle
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/Auth0FlutterMfaMethodCallHandler.kt
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/Auth0FlutterPlugin.kt
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/MfaExtensions.kt
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/ChallengeRequestHandler.kt
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollEmailRequestHandler.kt
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollPhoneRequestHandler.kt
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollPushRequestHandler.kt
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollTotpRequestHandler.kt
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/GetAuthenticatorsRequestHandler.kt
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/MfaRequestHandler.kt
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/VerifyRequestHandler.kt
  • auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/Auth0FlutterMfaMethodCallHandlerTest.kt
  • auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/ChallengeRequestHandlerTest.kt
  • auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollEmailRequestHandlerTest.kt
  • auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollPhoneRequestHandlerTest.kt
  • auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollPushRequestHandlerTest.kt
  • auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollTotpRequestHandlerTest.kt
  • auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/GetAuthenticatorsRequestHandlerTest.kt
  • auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/VerifyRequestHandlerTest.kt
  • auth0_flutter/darwin/Classes/SwiftAuth0FlutterPlugin.swift
  • auth0_flutter/darwin/auth0_flutter.podspec
  • auth0_flutter/ios/auth0_flutter.podspec
  • auth0_flutter/lib/auth0_flutter.dart
  • auth0_flutter/lib/src/mobile/mfa_api.dart
  • auth0_flutter/macos/auth0_flutter.podspec
  • auth0_flutter/test/mobile/mfa_api_test.dart
  • auth0_flutter_platform_interface/lib/auth0_flutter_platform_interface.dart
  • auth0_flutter_platform_interface/lib/src/auth/api_exception.dart
  • auth0_flutter_platform_interface/lib/src/mfa/auth0_flutter_mfa_platform.dart
  • auth0_flutter_platform_interface/lib/src/mfa/method_channel_auth0_flutter_mfa.dart
  • auth0_flutter_platform_interface/lib/src/mfa/mfa_authenticator.dart
  • auth0_flutter_platform_interface/lib/src/mfa/mfa_challenge.dart
  • auth0_flutter_platform_interface/lib/src/mfa/mfa_challenge_options.dart
  • auth0_flutter_platform_interface/lib/src/mfa/mfa_enroll_email_options.dart
  • auth0_flutter_platform_interface/lib/src/mfa/mfa_enroll_phone_options.dart
  • auth0_flutter_platform_interface/lib/src/mfa/mfa_enroll_push_options.dart
  • auth0_flutter_platform_interface/lib/src/mfa/mfa_enroll_totp_options.dart
  • auth0_flutter_platform_interface/lib/src/mfa/mfa_enrollment_challenge.dart
  • auth0_flutter_platform_interface/lib/src/mfa/mfa_exception.dart
  • auth0_flutter_platform_interface/lib/src/mfa/mfa_get_authenticators_options.dart
  • auth0_flutter_platform_interface/lib/src/mfa/mfa_requirements.dart
  • auth0_flutter_platform_interface/lib/src/mfa/mfa_verify_options.dart
  • auth0_flutter_platform_interface/test/api_exception_test.dart
  • auth0_flutter_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart

Comment on lines +47 to +51
MfaRequirements? get mfaRequirements =>
details['mfa_requirements'] is Map
? MfaRequirements.fromMap(
Map<String, dynamic>.from(details['mfa_requirements'] as Map))
: null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify strict Dart analyzer settings and locate raw/unsafe Map casts.
fd -i "analysis_options.yaml" "analysis_options.yml" | \
  xargs -r sed -n '1,220p' | rg -n "strict-(casts|inference|raw-types)"

rg -n "is Map\\b|as Map\\b|Map<String, dynamic>\\.from\\(" \
  auth0_flutter_platform_interface/lib/src/auth/api_exception.dart \
  auth0_flutter_platform_interface/lib/src/mfa/method_channel_auth0_flutter_mfa.dart

Repository: auth0/auth0-flutter

Length of output: 607


🏁 Script executed:

find . -name "analysis_options.yaml" -o -name "analysis_options.yml" | head -5

Repository: auth0/auth0-flutter

Length of output: 204


🏁 Script executed:

sed -n '40,55p' auth0_flutter_platform_interface/lib/src/auth/api_exception.dart

Repository: auth0/auth0-flutter

Length of output: 819


🏁 Script executed:

fd "mfa_requirements.dart" auth0_flutter_platform_interface/lib/src/mfa/

Repository: auth0/auth0-flutter

Length of output: 130


🏁 Script executed:

cat auth0_flutter_platform_interface/analysis_options.yaml | head -50

Repository: auth0/auth0-flutter

Length of output: 1559


🏁 Script executed:

cat auth0_flutter_platform_interface/lib/src/mfa/mfa_requirements.dart

Repository: auth0/auth0-flutter

Length of output: 1655


🏁 Script executed:

rg -l "mfa_requirements" auth0_flutter_platform_interface/test/ | head -5

Repository: auth0/auth0-flutter

Length of output: 125


🏁 Script executed:

rg "test.*mfa_requirements|mfa_requirements.*test" -A 3 auth0_flutter_platform_interface/test/src/auth/

Repository: auth0/auth0-flutter

Length of output: 151


🏁 Script executed:

rg "is Map|as Map" auth0_flutter_platform_interface/lib/src/auth/ -n

Repository: auth0/auth0-flutter

Length of output: 732


🏁 Script executed:

cat auth0_flutter_platform_interface/test/api_exception_test.dart

Repository: auth0/auth0-flutter

Length of output: 13378


Harden mfaRequirements parsing to prevent runtime crashes on malformed payloads.

The is Map check on line 48 is too loose and violates strict-raw-types. Even with this guard, MfaRequirements.fromMap() calls _parseFactors() which casts fields to List<Object?>? without validating structure—a payload with 'challenge': 'string' crashes at runtime with TypeError. No tests cover malformed nested data.

MFA error handling is authentication-critical; crashes here break the entire flow. Use stricter type checking and defensive error handling:

Proposed fix
-  MfaRequirements? get mfaRequirements =>
-      details['mfa_requirements'] is Map
-          ? MfaRequirements.fromMap(
-              Map<String, dynamic>.from(details['mfa_requirements'] as Map))
-          : null;
+  MfaRequirements? get mfaRequirements {
+    final Object? rawRequirements = details['mfa_requirements'];
+    if (rawRequirements is! Map<Object?, Object?>) {
+      return null;
+    }
+    try {
+      return MfaRequirements.fromMap(
+        Map<String, dynamic>.from(rawRequirements),
+      );
+    } on TypeError {
+      return null;
+    }
+  }
🤖 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_platform_interface/lib/src/auth/api_exception.dart` around
lines 47 - 51, The mfaRequirements getter uses a loose is Map check that doesn't
validate the actual structure of the nested data, and MfaRequirements.fromMap()
method along with its _parseFactors() helper perform unsafe casts without
validation, causing runtime crashes on malformed payloads. Replace the loose
type guard with stricter validation that checks the actual structure of the
mfa_requirements Map before passing it to MfaRequirements.fromMap(), and add
try-catch error handling around the MfaRequirements instantiation to gracefully
handle malformed nested data (such as 'challenge' being a string instead of the
expected structure) rather than crashing. Additionally, make the _parseFactors()
method more defensive by validating field types before casting to List instead
of assuming the structure is correct.

Source: Coding guidelines

Comment on lines +36 to +39
return result
.map((final item) =>
MfaAuthenticator.fromMap(Map<String, dynamic>.from(item as Map)))
.toList();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify strict cast settings and locate unchecked cast patterns in MFA channel code.
fd -i "analysis_options.yaml" "analysis_options.yml" | \
  xargs -r sed -n '1,220p' | rg -n "strict-(casts|inference|raw-types)"

rg -n "as Map\\b| is Map\\b|invokeListMethod\\(|invokeMapMethod\\(" \
  auth0_flutter_platform_interface/lib/src/mfa/method_channel_auth0_flutter_mfa.dart

Repository: auth0/auth0-flutter

Length of output: 423


🏁 Script executed:

cat -n auth0_flutter_platform_interface/lib/src/mfa/method_channel_auth0_flutter_mfa.dart | sed -n '30,45p'

Repository: auth0/auth0-flutter

Length of output: 739


🏁 Script executed:

fd "analysis_options.yaml" auth0_flutter_platform_interface

Repository: auth0/auth0-flutter

Length of output: 118


🏁 Script executed:

rg -n "class MfaException|const.*unknown" auth0_flutter_platform_interface/lib/src/mfa/ -A 2

Repository: auth0/auth0-flutter

Length of output: 1386


🏁 Script executed:

rg -n "as Map|as List" auth0_flutter_platform_interface/lib/src/mfa/method_channel_auth0_flutter_mfa.dart

Repository: auth0/auth0-flutter

Length of output: 144


🏁 Script executed:

cat -n auth0_flutter_platform_interface/analysis_options.yaml

Repository: auth0/auth0-flutter

Length of output: 3868


🏁 Script executed:

rg -n "fromMap" auth0_flutter_platform_interface/lib/src/mfa/mfa_authenticator.dart | head -5

Repository: auth0/auth0-flutter

Length of output: 139


🏁 Script executed:

rg -n "_invokeListRequest" auth0_flutter_platform_interface/lib/src/mfa/method_channel_auth0_flutter_mfa.dart -B 2 -A 10

Repository: auth0/auth0-flutter

Length of output: 999


Guard list item decoding before casting in getAuthenticators.

Line 38 uses an unchecked cast item as Map which violates the project's strict-casts: true setting. A malformed native payload will throw TypeError instead of MfaException, breaking the expected MFA error contract.

Proposed fix
-    return result
-        .map((final item) =>
-            MfaAuthenticator.fromMap(Map<String, dynamic>.from(item as Map)))
-        .toList();
+    return result.map((final item) {
+      if (item is! Map<Object?, Object?>) {
+        throw const MfaException.unknown(
+          'Channel returned an invalid authenticator item.',
+        );
+      }
+      return MfaAuthenticator.fromMap(Map<String, dynamic>.from(item));
+    }).toList(growable: false);
🤖 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_platform_interface/lib/src/mfa/method_channel_auth0_flutter_mfa.dart`
around lines 36 - 39, The unchecked cast `item as Map` in the
`getAuthenticators` method violates the strict-casts setting and causes
malformed native payloads to throw TypeError instead of MfaException. Guard the
item variable before casting by validating it is a Map type, and throw an
appropriate MfaException if validation fails, ensuring consistent error handling
that aligns with the project's error contract.

Source: Coding guidelines

Comment on lines +38 to +46
factory MfaAuthenticator.fromMap(final Map<String, dynamic> result) =>
MfaAuthenticator(
id: result['id'] as String,
type: result['type'] as String?,
authenticatorType: result['authenticator_type'] as String?,
active: result['active'] as bool? ?? false,
oobChannel: result['oob_channel'] as String?,
name: result['name'] as String?,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the mfa_authenticator.dart file
fd -t f "mfa_authenticator.dart" auth0_flutter_platform_interface/

Repository: auth0/auth0-flutter

Length of output: 131


🏁 Script executed:

# Read the file to see the current state of the code
cat -n auth0_flutter_platform_interface/lib/src/mfa/mfa_authenticator.dart

Repository: auth0/auth0-flutter

Length of output: 2203


🏁 Script executed:

# Check for other fromMap methods in the platform interface that might have similar patterns
rg "fromMap" auth0_flutter_platform_interface/lib/ -A 10 -B 2

Repository: auth0/auth0-flutter

Length of output: 50375


🏁 Script executed:

# Check if there are any existing validation helpers in the codebase
rg "_readRequired|_readNullable" auth0_flutter_platform_interface/

Repository: auth0/auth0-flutter

Length of output: 45


🏁 Script executed:

# Look at the full MfaAuthenticator class definition to understand the constructor
ast-grep --pattern 'class MfaAuthenticator {
  $$$
}'

Repository: auth0/auth0-flutter

Length of output: 5544


Remove unchecked as casts at the platform boundary.

The fromMap factory uses six unchecked as casts on dynamic data (lines 40–45). Per coding guidelines, use type guards (is checks) before casting. Malformed native payloads will surface as uncontrolled TypeError instead of proper SDK exceptions.

Proposed fix
 factory MfaAuthenticator.fromMap(final Map<String, dynamic> result) =>
-    MfaAuthenticator(
-      id: result['id'] as String,
-      type: result['type'] as String?,
-      authenticatorType: result['authenticator_type'] as String?,
-      active: result['active'] as bool? ?? false,
-      oobChannel: result['oob_channel'] as String?,
-      name: result['name'] as String?,
-    );
+    MfaAuthenticator(
+      id: _readRequiredString(result, 'id'),
+      type: _readNullableString(result, 'type'),
+      authenticatorType: _readNullableString(result, 'authenticator_type'),
+      active: _readNullableBool(result, 'active') ?? false,
+      oobChannel: _readNullableString(result, 'oob_channel'),
+      name: _readNullableString(result, 'name'),
+    );
+
+static String _readRequiredString(Map<String, dynamic> map, String key) {
+  final value = map[key];
+  if (value is String) return value;
+  throw ArgumentError.value(value, key, 'Expected non-null String');
+}
+
+static String? _readNullableString(Map<String, dynamic> map, String key) {
+  final value = map[key];
+  if (value == null || value is String) return value;
+  throw ArgumentError.value(value, key, 'Expected String?');
+}
+
+static bool? _readNullableBool(Map<String, dynamic> map, String key) {
+  final value = map[key];
+  if (value == null || value is bool) return value;
+  throw ArgumentError.value(value, key, 'Expected bool?');
+}
🤖 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_platform_interface/lib/src/mfa/mfa_authenticator.dart` around
lines 38 - 46, The MfaAuthenticator.fromMap factory method uses unchecked as
casts directly on dynamic data from the native platform, which will cause
uncontrolled TypeErrors if the payload is malformed. Replace each unchecked as
cast (for id, type, authenticatorType, active, oobChannel, and name) with proper
type guards using is checks before casting. For each field, verify the type
using an is check, then cast only if the type matches, and provide an
appropriate default value or handle the error case appropriately to ensure
malformed native payloads are caught and handled with proper SDK exceptions
rather than raw TypeErrors.

Source: Coding guidelines

Comment on lines +22 to +27
factory MfaChallenge.fromMap(final Map<String, dynamic> result) =>
MfaChallenge(
challengeType: result['challenge_type'] as String,
oobCode: result['oob_code'] as String?,
bindingMethod: result['binding_method'] as String?,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find and examine the MfaChallenge file
find . -type f -name "mfa_challenge.dart" | head -5

Repository: auth0/auth0-flutter

Length of output: 129


🏁 Script executed:

# Once found, let's check the specific lines
cat -n auth0_flutter_platform_interface/lib/src/mfa/mfa_challenge.dart | sed -n '20,35p'

Repository: auth0/auth0-flutter

Length of output: 599


🏁 Script executed:

# Search for similar unsafe cast patterns in platform interface Dart files
rg "as String\?" auth0_flutter_platform_interface --type dart -n

Repository: auth0/auth0-flutter

Length of output: 7460


🏁 Script executed:

# Check if there are existing helper methods for type validation in similar files
find auth0_flutter_platform_interface -name "*.dart" -type f | xargs grep -l "_readRequired\|_readNullable" 2>/dev/null | head -5

Repository: auth0/auth0-flutter

Length of output: 45


🏁 Script executed:

# Check for tests covering MfaChallenge
find . -name "*.dart" -type f | xargs grep -l "MfaChallenge.fromMap\|mfa_challenge_test" 2>/dev/null

Repository: auth0/auth0-flutter

Length of output: 297


🏁 Script executed:

# Check the extent of the issue - count unsafe casts in platform interface across all fromMap methods
rg "factory.*fromMap.*Map.*dynamic" auth0_flutter_platform_interface --type dart -c | sort

Repository: auth0/auth0-flutter

Length of output: 1088


🏁 Script executed:

# Check the test file to see if it tests malformed inputs
cat -n auth0_flutter_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart | grep -A 20 -B 5 "MfaChallenge.fromMap\|malformed\|invalid" | head -60

Repository: auth0/auth0-flutter

Length of output: 1053


🏁 Script executed:

# Look at the entire test structure for MfaChallenge
grep -n "test\|MfaChallenge" auth0_flutter_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart | head -20

Repository: auth0/auth0-flutter

Length of output: 1193


Add type guards to prevent crash on malformed method-channel responses.

Lines 24–26 use unchecked as casts. If the native layer returns incorrect types (or the contract drifts), these casts will throw unexpectedly instead of raising a descriptive error. For an Auth0 SDK deserializing security-critical data (challenges, tokens), this must fail safely and predictably.

Tests currently only verify the happy path—add type validation helpers as shown in the proposed diff, or equivalent error handling that uses is guards before casting, per coding guidelines.

Proposed fix
 factory MfaChallenge.fromMap(final Map<String, dynamic> result) =>
     MfaChallenge(
-      challengeType: result['challenge_type'] as String,
-      oobCode: result['oob_code'] as String?,
-      bindingMethod: result['binding_method'] as String?,
+      challengeType: _readRequiredString(result, 'challenge_type'),
+      oobCode: _readNullableString(result, 'oob_code'),
+      bindingMethod: _readNullableString(result, 'binding_method'),
     );
+
+static String _readRequiredString(Map<String, dynamic> map, String key) {
+  final value = map[key];
+  if (value is String) return value;
+  throw ArgumentError.value(value, key, 'Expected non-null String');
+}
+
+static String? _readNullableString(Map<String, dynamic> map, String key) {
+  final value = map[key];
+  if (value == null || value is String) return value;
+  throw ArgumentError.value(value, key, 'Expected String?');
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
factory MfaChallenge.fromMap(final Map<String, dynamic> result) =>
MfaChallenge(
challengeType: result['challenge_type'] as String,
oobCode: result['oob_code'] as String?,
bindingMethod: result['binding_method'] as String?,
);
factory MfaChallenge.fromMap(final Map<String, dynamic> result) =>
MfaChallenge(
challengeType: _readRequiredString(result, 'challenge_type'),
oobCode: _readNullableString(result, 'oob_code'),
bindingMethod: _readNullableString(result, 'binding_method'),
);
static String _readRequiredString(Map<String, dynamic> map, String key) {
final value = map[key];
if (value is String) return value;
throw ArgumentError.value(value, key, 'Expected non-null String');
}
static String? _readNullableString(Map<String, dynamic> map, String key) {
final value = map[key];
if (value == null || value is String) return value;
throw ArgumentError.value(value, key, 'Expected String?');
}
🤖 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_platform_interface/lib/src/mfa/mfa_challenge.dart` around lines
22 - 27, The MfaChallenge.fromMap factory constructor uses unchecked `as` casts
for the challengeType, oobCode, and bindingMethod fields, which will throw
unexpectedly if the native layer returns incorrect types. Add type validation
using `is` guards to check each value's type before casting, and handle cases
where types don't match by raising descriptive errors that explain the expected
type and what was actually received. This ensures the Auth0 SDK fails safely and
predictably when deserializing security-critical challenge data.

Source: Coding guidelines

Comment on lines +63 to +76
factory MfaEnrollmentChallenge.fromMap(final Map<String, dynamic> result) =>
MfaEnrollmentChallenge(
authenticatorType: result['authenticator_type'] as String?,
oobChannel: result['oob_channel'] as String?,
oobCode: result['oob_code'] as String?,
bindingMethod: result['binding_method'] as String?,
totpSecret: result['totp_secret'] as String?,
barcodeUri: result['barcode_uri'] as String?,
recoveryCodes: (result['recovery_codes'] as List<Object?>?)
?.map((final e) => e as String)
.toList(),
id: result['id'] as String?,
authSession: result['auth_session'] as String?,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the file mentioned in the review
fd -t f "mfa_enrollment_challenge.dart" --exec wc -l {} \;

Repository: auth0/auth0-flutter

Length of output: 143


🏁 Script executed:

# Check if file exists and read the relevant lines
cat -n auth0_flutter_platform_interface/lib/src/mfa/mfa_enrollment_challenge.dart | sed -n '55,85p'

Repository: auth0/auth0-flutter

Length of output: 1398


🏁 Script executed:

# Check Dart analysis settings in the project
find . -name "analysis_options.yaml" -o -name "pubspec.yaml" | head -5

Repository: auth0/auth0-flutter

Length of output: 270


🏁 Script executed:

# Look for test files related to this class
fd -t f "*test*" -name "*mfa*enrollment*"

Repository: auth0/auth0-flutter

Length of output: 294


🏁 Script executed:

# Search for similar fromMap patterns in the same directory
rg "factory.*fromMap" auth0_flutter_platform_interface/lib/src/ -A 10 | head -80

Repository: auth0/auth0-flutter

Length of output: 8599


🏁 Script executed:

# Check the analysis_options.yaml for the platform_interface package
cat auth0_flutter_platform_interface/analysis_options.yaml

Repository: auth0/auth0-flutter

Length of output: 3217


🏁 Script executed:

# Look for tests related to MfaEnrollmentChallenge
find . -type f -name "*.dart" -path "*/test/*" | xargs grep -l "MfaEnrollmentChallenge" 2>/dev/null

Repository: auth0/auth0-flutter

Length of output: 244


🏁 Script executed:

# Check line lengths in the current file
rg --max-count=20 "." auth0_flutter_platform_interface/lib/src/mfa/mfa_enrollment_challenge.dart | awk '{print length, NR, $0}' | sort -rn | head -20

Repository: auth0/auth0-flutter

Length of output: 1144


🏁 Script executed:

# Check if strict-casts and related analysis is configured
grep -A 20 "rules:" auth0_flutter_platform_interface/analysis_options.yaml

Repository: auth0/auth0-flutter

Length of output: 816


🏁 Script executed:

# Check tests for MfaEnrollmentChallenge.fromMap
grep -A 30 "MfaEnrollmentChallenge" auth0_flutter_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart

Repository: auth0/auth0-flutter

Length of output: 1653


🏁 Script executed:

# Verify the suggested fix doesn't violate line length limits
python3 << 'PY'
suggested_code = """  factory MfaEnrollmentChallenge.fromMap(final Map<String, dynamic> result) {
    final recoveryRaw = result['recovery_codes'];
    return MfaEnrollmentChallenge(
      authenticatorType: result['authenticator_type'] is String
          ? result['authenticator_type'] as String
          : null,
      oobChannel:
          result['oob_channel'] is String ? result['oob_channel'] as String : null,
      oobCode: result['oob_code'] is String ? result['oob_code'] as String : null,
      bindingMethod: result['binding_method'] is String
          ? result['binding_method'] as String
          : null,
      totpSecret:
          result['totp_secret'] is String ? result['totp_secret'] as String : null,
      barcodeUri:
          result['barcode_uri'] is String ? result['barcode_uri'] as String : null,
      recoveryCodes: recoveryRaw is List
          ? recoveryRaw.whereType<String>().toList()
          : null,
      id: result['id'] is String ? result['id'] as String : null,
      authSession:
          result['auth_session'] is String ? result['auth_session'] as String : null,
    );
  }"""

for i, line in enumerate(suggested_code.split('\n'), 1):
    length = len(line)
    if length > 80:
        print(f"Line {i}: {length} chars: {line}")
PY

Repository: auth0/auth0-flutter

Length of output: 577


🏁 Script executed:

# Check what the test file tests
cat -n auth0_flutter_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart | head -100

Repository: auth0/auth0-flutter

Length of output: 4085


Guard dynamic payload types in fromMap to prevent runtime cast failures.

Lines 63-76 use unchecked as casts on platform channel values, violating the strict-casts guideline. If the native layer returns a non-string value (e.g., an integer in recovery_codes array), the cast throws at runtime. Use is checks or whereType before casting.

Note: The suggested implementation requires // ignore_for_file: lines_longer_than_80_chars at the top of the file, as guard-based type narrowing exceeds the 80-character line limit enforced by your linter. Alternatively, extract the recovery codes parsing to a helper method to keep lines under 80 characters.

🤖 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_platform_interface/lib/src/mfa/mfa_enrollment_challenge.dart`
around lines 63 - 76, The MfaEnrollmentChallenge.fromMap factory method uses
unchecked `as` casts on dynamic platform channel values which can throw runtime
exceptions if the native layer returns unexpected types. Guard each cast with an
`is` type check before casting, or use `whereType` for the recovery_codes list
parsing to filter elements to only Strings before converting to the list. This
prevents runtime failures when non-string values are received from the platform
channel.

Source: Coding guidelines

Comment on lines +41 to +84
@Test
fun `should call result success with challenge on success`() {
val handler = ChallengeRequestHandler()
val mockResult = mock<Result>()
val mockAccount = mock<Auth0>()
val mockClient = mock<MfaApiClient>()
val mockRequest = mock<Request<Challenge, MfaChallengeException>>()
val request = MethodCallRequest(
account = mockAccount,
hashMapOf<String, Any>(
"mfaToken" to "mfa-token",
"authenticatorId" to "sms|dev_1"
)
)

val challenge = mock<Challenge>()
whenever(challenge.challengeType).thenReturn("oob")
whenever(challenge.oobCode).thenReturn("oob-code")
whenever(challenge.bindingMethod).thenReturn("prompt")

whenever(mockClient.challenge(any())).thenReturn(mockRequest)
doAnswer {
val callback = it.getArgument<Callback<Challenge, MfaChallengeException>>(0)
callback.onSuccess(challenge)
}.whenever(mockRequest).start(any())

handler.handle(mockClient, request, mockResult)

verify(mockResult).success(any())
}

@Test(expected = IllegalArgumentException::class)
fun `should throw when authenticatorId is missing`() {
val handler = ChallengeRequestHandler()
val mockResult = mock<Result>()
val mockAccount = mock<Auth0>()
val mockClient = mock<MfaApiClient>()
val request = MethodCallRequest(
account = mockAccount,
hashMapOf<String, Any>("mfaToken" to "mfa-token")
)

handler.handle(mockClient, request, mockResult)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add a failure-path test that asserts result.error(...) is invoked.

Current coverage misses the onFailure callback mapping in ChallengeRequestHandler;
a regression here would silently break auth error propagation.

As per coding guidelines, auth errors in Android handlers must be surfaced through result.error, not dropped.

🤖 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/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/ChallengeRequestHandlerTest.kt`
around lines 41 - 84, The test class ChallengeRequestHandlerTest is missing
coverage for the failure path in the ChallengeRequestHandler.handle() method.
Add a new test method that sets up the same mocks (mockClient, mockRequest,
mockResult, mockAccount) as the existing success test, but instead configure the
doAnswer callback to invoke onFailure with a MfaChallengeException. Then verify
that mockResult.error() is called to ensure authentication errors are properly
propagated through the result object rather than being silently dropped. This
test ensures that the onFailure callback path in the handler is working
correctly.

Source: Coding guidelines

Comment on lines +21 to +40
fun `should call enroll with Phone type`() {
val handler = EnrollPhoneRequestHandler()
val mockResult = mock<Result>()
val mockAccount = mock<Auth0>()
val mockClient = mock<MfaApiClient>()
val mockRequest = mock<Request<EnrollmentChallenge, MfaEnrollmentException>>()
val request = MethodCallRequest(
account = mockAccount,
hashMapOf<String, Any>(
"mfaToken" to "mfa-token",
"phoneNumber" to "+1234567890"
)
)

whenever(mockClient.enroll(any())).thenReturn(mockRequest)

handler.handle(mockClient, request, mockResult)

verify(mockClient).enroll(eq(MfaEnrollmentType.Phone("+1234567890")))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add coverage for type forwarding in enrollPhone.

This test only proves phoneNumber is used. It should also include and assert
the type path (SMS vs voice), otherwise the channel contract regression is
not detectable.

🤖 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/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollPhoneRequestHandlerTest.kt`
around lines 21 - 40, The test in EnrollPhoneRequestHandler only verifies that
the phoneNumber parameter is forwarded to the enroll method, but does not test
the type parameter (SMS vs voice). Add a "type" entry to the hashMap in the
MethodCallRequest constructor (with a value like "SMS" or "voice"), then update
the verify statement to ensure that both the phoneNumber and type are correctly
passed together to the enroll method call, so that regressions in the type
forwarding can be detected.

Comment on lines +26 to +84
@Test
fun `should call verify with Otp type`() {
val handler = VerifyRequestHandler()
val mockResult = mock<Result>()
val mockClient = mock<MfaApiClient>()
val mockRequest = mock<Request<Credentials, MfaVerifyException>>()

whenever(mockClient.verify(any())).thenReturn(mockRequest)

handler.handle(
mockClient,
requestWith(mapOf("grantType" to "otp", "otp" to "123456")),
mockResult
)

verify(mockClient).verify(eq(MfaVerificationType.Otp("123456")))
}

@Test
fun `should call verify with Oob type and binding code`() {
val handler = VerifyRequestHandler()
val mockResult = mock<Result>()
val mockClient = mock<MfaApiClient>()
val mockRequest = mock<Request<Credentials, MfaVerifyException>>()

whenever(mockClient.verify(any())).thenReturn(mockRequest)

handler.handle(
mockClient,
requestWith(
mapOf(
"grantType" to "oob",
"oobCode" to "oob-code",
"bindingCode" to "000111"
)
),
mockResult
)

verify(mockClient).verify(eq(MfaVerificationType.Oob("oob-code", "000111")))
}

@Test
fun `should call verify with RecoveryCode type`() {
val handler = VerifyRequestHandler()
val mockResult = mock<Result>()
val mockClient = mock<MfaApiClient>()
val mockRequest = mock<Request<Credentials, MfaVerifyException>>()

whenever(mockClient.verify(any())).thenReturn(mockRequest)

handler.handle(
mockClient,
requestWith(mapOf("grantType" to "recovery_code", "recoveryCode" to "ABCD")),
mockResult
)

verify(mockClient).verify(eq(MfaVerificationType.RecoveryCode("ABCD")))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add negative tests for non-String payload types in verify inputs.

Please add cases where grantType/otp/oobCode/recoveryCode are wrong types,
and assert validation behavior. This locks the cast-safety contract.

As per coding guidelines, MethodChannel handlers must guard against unsafe cast crashes.

Also applies to: 144-151

🤖 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/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/VerifyRequestHandlerTest.kt`
around lines 26 - 84, Add negative test cases to validate type safety in the
VerifyRequestHandler for cases where the payload contains non-String types for
the required fields. Create additional test methods that pass incorrect types
(such as Integer, Boolean, or Map instead of String) for grantType, otp,
oobCode, and recoveryCode parameters to the handle method of
VerifyRequestHandler. Assert that these invalid inputs are properly validated
and rejected by verifying that either an appropriate error is passed to the
mockResult object or that the mockClient.verify method is not called, ensuring
the handler guards against unsafe cast crashes as required by the MethodChannel
handler guidelines.

Source: Coding guidelines

Comment thread auth0_flutter/EXAMPLES.md
Comment on lines +91 to +103
test('enrollPhone forwards phoneNumber and type', () async {
when(mockedPlatform.enrollPhone(any))
.thenAnswer((final _) async => TestPlatform.enrollmentChallenge);

await mfa()
.enrollPhone(phoneNumber: '+1234567890', type: PhoneType.voice);

final captured = verify(mockedPlatform.enrollPhone(captureAny))
.captured
.single as ApiRequest<MfaEnrollPhoneOptions>;
expect(captured.options.phoneNumber, '+1234567890');
expect(captured.options.type, PhoneType.voice);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add tests for default/optional MFA branches.

Please add assertions for enrollPhone() defaulting to PhoneType.sms and
verifyOob() when bindingCode is omitted, so both public API paths are
covered.

As per coding guidelines, "**/*.dart ... Check that new public symbols are
covered by tests."

Also applies to: 157-169

🤖 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/test/mobile/mfa_api_test.dart` around lines 91 - 103, Add test
cases to cover the default parameter branches for the MFA API. Create a new test
method for enrollPhone that omits the type parameter and verify that it defaults
to PhoneType.sms by capturing and asserting the captured request options.
Additionally, add a test method for verifyOob that omits the bindingCode
parameter and verify the captured request reflects this omission. These tests
ensure both the explicit parameter paths and the default parameter paths are
covered as required by the coding guidelines for public API symbols.

Source: Coding guidelines

@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

🤖 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/darwin/Classes/MfaAPI/MfaEnrollPhoneMethodHandler.swift`:
- Around line 16-22: Extract the `type` argument from the arguments dictionary
in the same way phoneNumber is extracted (using a guard statement), validating
that it is a String and corresponds to either "sms" or "voice". Then pass this
type parameter to the .enroll() method call in MfaEnrollPhoneMethodHandler to
ensure both SMS and voice factor enrollment methods are properly routed based on
the specified type.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 626e621a-acdd-4a15-bffb-d34eebb3a279

📥 Commits

Reviewing files that changed from the base of the PR and between 20ef247 and 07f25d8.

⛔ Files ignored due to path filters (18)
  • auth0_flutter/ios/Classes/MfaAPI/MfaChallengeMethodHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaEnrollEmailMethodHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaEnrollPhoneMethodHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaEnrollPushMethodHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaEnrollTotpMethodHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaExtensions.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaGetAuthenticatorsMethodHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/ios/Classes/MfaAPI/MfaVerifyMethodHandler.swift is excluded by !auth0_flutter/ios/Classes/**
  • auth0_flutter/macos/Classes/MfaAPI/MfaChallengeMethodHandler.swift is excluded by !auth0_flutter/macos/Classes/**
  • auth0_flutter/macos/Classes/MfaAPI/MfaEnrollEmailMethodHandler.swift is excluded by !auth0_flutter/macos/Classes/**
  • auth0_flutter/macos/Classes/MfaAPI/MfaEnrollPhoneMethodHandler.swift is excluded by !auth0_flutter/macos/Classes/**
  • auth0_flutter/macos/Classes/MfaAPI/MfaEnrollPushMethodHandler.swift is excluded by !auth0_flutter/macos/Classes/**
  • auth0_flutter/macos/Classes/MfaAPI/MfaEnrollTotpMethodHandler.swift is excluded by !auth0_flutter/macos/Classes/**
  • auth0_flutter/macos/Classes/MfaAPI/MfaExtensions.swift is excluded by !auth0_flutter/macos/Classes/**
  • auth0_flutter/macos/Classes/MfaAPI/MfaGetAuthenticatorsMethodHandler.swift is excluded by !auth0_flutter/macos/Classes/**
  • auth0_flutter/macos/Classes/MfaAPI/MfaHandler.swift is excluded by !auth0_flutter/macos/Classes/**
  • auth0_flutter/macos/Classes/MfaAPI/MfaVerifyMethodHandler.swift is excluded by !auth0_flutter/macos/Classes/**
📒 Files selected for processing (11)
  • auth0_flutter/EXAMPLES.md
  • auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/VerifyRequestHandlerTest.kt
  • auth0_flutter/darwin/Classes/MfaAPI/MfaChallengeMethodHandler.swift
  • auth0_flutter/darwin/Classes/MfaAPI/MfaEnrollEmailMethodHandler.swift
  • auth0_flutter/darwin/Classes/MfaAPI/MfaEnrollPhoneMethodHandler.swift
  • auth0_flutter/darwin/Classes/MfaAPI/MfaEnrollPushMethodHandler.swift
  • auth0_flutter/darwin/Classes/MfaAPI/MfaEnrollTotpMethodHandler.swift
  • auth0_flutter/darwin/Classes/MfaAPI/MfaExtensions.swift
  • auth0_flutter/darwin/Classes/MfaAPI/MfaGetAuthenticatorsMethodHandler.swift
  • auth0_flutter/darwin/Classes/MfaAPI/MfaHandler.swift
  • auth0_flutter/darwin/Classes/MfaAPI/MfaVerifyMethodHandler.swift

Comment on lines +16 to +22
guard let phoneNumber = arguments["phoneNumber"] as? String else {
return callback(FlutterError(from: .requiredArgumentMissing("phoneNumber")))
}

client
.enroll(mfaToken: mfaToken, phoneNumber: phoneNumber)
.start {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Honor type when enrolling phone factors.

Line 21 ignores arguments["type"] and always calls phone enrollment without factor type selection. That breaks the MFA API contract (sms vs voice) and makes voice enrollment unreachable on iOS/macOS.

🤖 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/darwin/Classes/MfaAPI/MfaEnrollPhoneMethodHandler.swift` around
lines 16 - 22, Extract the `type` argument from the arguments dictionary in the
same way phoneNumber is extracted (using a guard statement), validating that it
is a String and corresponds to either "sms" or "voice". Then pass this type
parameter to the .enroll() method call in MfaEnrollPhoneMethodHandler to ensure
both SMS and voice factor enrollment methods are properly routed based on the
specified type.

@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

🤖 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/lib/src/web/auth0_flutter_plugin_real.dart`:
- Around line 259-263: The MFA challenge-type detection in the challengeType
variable assignment is using the incorrect `totp|` prefix when it should check
for `otp|` according to the spa-js API documentation. Change the startsWith
check from `'totp|'` to `'otp|'` to correctly identify OTP authenticators and
assign the proper challenge type to the SDK. Additionally, add a regression test
case that validates the behavior when authenticatorId uses the `otp|` prefix to
ensure this branch is properly covered and prevent future regressions.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 376a5bac-65b7-4364-84f5-a6705f91b524

📥 Commits

Reviewing files that changed from the base of the PR and between 07f25d8 and 6e80866.

⛔ Files ignored due to path filters (2)
  • auth0_flutter/example/ios/Tests/Mfa/MfaGetAuthenticatorsMethodHandlerTests.swift is excluded by !**/example/**
  • auth0_flutter/example/web/index.html is excluded by !**/example/**
📒 Files selected for processing (10)
  • auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/Auth0FlutterPluginTest.kt
  • auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/VerifyRequestHandlerTest.kt
  • auth0_flutter/lib/auth0_flutter_web.dart
  • auth0_flutter/lib/src/web/auth0_flutter_plugin_real.dart
  • auth0_flutter/lib/src/web/auth0_flutter_web_platform_proxy.dart
  • auth0_flutter/lib/src/web/extensions/mfa_extensions.dart
  • auth0_flutter/lib/src/web/js_interop.dart
  • auth0_flutter/lib/src/web/mfa_web.dart
  • auth0_flutter/test/web/mfa_web_test.dart
  • auth0_flutter_platform_interface/lib/src/auth0_flutter_web_platform.dart

Comment thread auth0_flutter/lib/src/web/auth0_flutter_plugin_real.dart
)
}

fun MfaException.toMfaMap(): Map<String, Any> = buildMap {

@pmathew92 pmathew92 Jun 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All type of MFAExceptions has code and description too which is what actually carries the error . Add those two

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done. MfaException.toMfaMap() now surfaces both code and description (the actual error carried by the native SDK) in the details map, in addition to the top-level result.error code/message:

put("code", getCode())
put("description", getDescription())
I also mirrored this on iOS/macOS (MfaExtensions.swift) so the Dart layer gets the same code/description contract on every platform.

- Drop unsupported voice  from mobile enrollPhone (SMS-only)
- Require factorsAllowed on getAuthenticators to match native validation
- Derive Android isNetworkError from network_error code (fixes isRetryable)
- Surface native error code/description in MFA error map (Android + iOS)
- Fix web challenge-type prefix: otp| not totp|
- Fix duplicate credentials declarations in EXAMPLES.md
- Add MfaException, MfaExtensions, and empty-factors tests

@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: 2

♻️ Duplicate comments (2)
auth0_flutter_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart (2)

18-91: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add tests for remaining new public MFA options/API paths.

Missing test coverage for:

  • MfaEnrollPushOptions.toMap() serialization
  • MfaEnrollTotpOptions.toMap() serialization
  • enrollEmail MethodChannel invocation
  • enrollPush MethodChannel invocation

As per path instructions, "Check that new public symbols are covered by tests."

🤖 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_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart`
around lines 18 - 91, Add missing test coverage for the new MFA options and
MethodChannel invocations. Within the 'options.toMap' test group, add tests for
MfaEnrollPushOptions and MfaEnrollTotpOptions serialization following the same
pattern as the existing tests (instantiate the options objects with appropriate
parameters and verify that toMap() returns the expected key-value pairs).
Additionally, add separate tests outside this group to verify that the
enrollEmail and enrollPush MethodChannel invocations are properly forwarded to
the platform channel with the correct method names and arguments, similar to how
other MethodChannel invocations are tested in the file.

Source: Path instructions


209-209: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Replace unsafe as Map cast with typed extraction using is guard.

The cast (log.single.arguments as Map) at line 209 lacks a type guard. With strict-casts enabled, use an is Map guard or Map<String, dynamic>.from() to prevent runtime crashes if the method channel returns unexpected types.

Also applies to lines 231 and 261.

🛡️ Proposed fix
-      expect((log.single.arguments as Map)['factorsAllowed'], ['oob']);
+      expect(
+        log.single.arguments is Map<String, dynamic>
+          ? (log.single.arguments as Map<String, dynamic>)['factorsAllowed']
+          : null,
+        ['oob']);

Or use safer extraction:

-      expect((log.single.arguments as Map)['factorsAllowed'], ['oob']);
+      final args = Map<String, dynamic>.from(log.single.arguments as Map);
+      expect(args['factorsAllowed'], ['oob']);

As per path instructions, "Flag any missing null checks and unsafe casts (use is checks/pattern matching before casting; use as only after guards)."

🤖 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_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart`
at line 209, The unsafe `as Map` casts in the expect statements at lines 209,
231, and 261 lack type guards and can cause runtime crashes if the method
channel returns unexpected types. Replace these casts by adding an `is Map`
guard before the cast or use `Map<String, dynamic>.from()` to safely extract the
arguments. Apply this fix to all three occurrences where `(log.single.arguments
as Map)` is used to ensure type safety with strict-casts enabled.

Source: Path instructions

🤖 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/lib/src/mobile/mfa_api.dart`:
- Around line 61-65: The getAuthenticators method does not validate that the
factorsAllowed parameter is non-empty at the SDK boundary, causing failures
later with generic invalid_request errors. Add an ArgumentError validation at
the beginning of the getAuthenticators method to check if factorsAllowed is
empty and throw an ArgumentError with a clear message if it is, ensuring
deterministic feedback to the caller before making the platform call.

In `@auth0_flutter/test/mobile/mfa_api_test.dart`:
- Around line 91-95: The enrollPhone test in the mfa_api_test.dart file
currently only verifies that phoneNumber is forwarded to the platform layer, but
it should also assert that mfaToken is being forwarded correctly. After the
existing call to mfa().enrollPhone(phoneNumber: '+1234567890'), add an assertion
that captures the arguments passed to mockedPlatform.enrollPhone and verifies
that the captured.options.mfaToken value is present and correctly forwarded to
prevent silent token-forwarding regressions.

---

Duplicate comments:
In
`@auth0_flutter_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart`:
- Around line 18-91: Add missing test coverage for the new MFA options and
MethodChannel invocations. Within the 'options.toMap' test group, add tests for
MfaEnrollPushOptions and MfaEnrollTotpOptions serialization following the same
pattern as the existing tests (instantiate the options objects with appropriate
parameters and verify that toMap() returns the expected key-value pairs).
Additionally, add separate tests outside this group to verify that the
enrollEmail and enrollPush MethodChannel invocations are properly forwarded to
the platform channel with the correct method names and arguments, similar to how
other MethodChannel invocations are tested in the file.
- Line 209: The unsafe `as Map` casts in the expect statements at lines 209,
231, and 261 lack type guards and can cause runtime crashes if the method
channel returns unexpected types. Replace these casts by adding an `is Map`
guard before the cast or use `Map<String, dynamic>.from()` to safely extract the
arguments. Apply this fix to all three occurrences where `(log.single.arguments
as Map)` is used to ensure type safety with strict-casts enabled.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0652896f-dcde-4de6-b287-057d81eb5e73

📥 Commits

Reviewing files that changed from the base of the PR and between 6e80866 and 99461eb.

📒 Files selected for processing (12)
  • auth0_flutter/EXAMPLES.md
  • auth0_flutter/README.md
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/MfaExtensions.kt
  • auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/MfaExtensionsTest.kt
  • auth0_flutter/darwin/Classes/MfaAPI/MfaExtensions.swift
  • auth0_flutter/lib/src/mobile/mfa_api.dart
  • auth0_flutter/lib/src/web/auth0_flutter_plugin_real.dart
  • auth0_flutter/test/mobile/mfa_api_test.dart
  • auth0_flutter_platform_interface/lib/src/mfa/mfa_enroll_phone_options.dart
  • auth0_flutter_platform_interface/lib/src/mfa/mfa_get_authenticators_options.dart
  • auth0_flutter_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart
  • auth0_flutter_platform_interface/test/mfa_exception_test.dart

Comment on lines +61 to +65
Future<List<MfaAuthenticator>> getAuthenticators(
{required final List<String> factorsAllowed}) =>
Auth0FlutterMfaPlatform.instance.getAuthenticators(_createApiRequest(
MfaGetAuthenticatorsOptions(
mfaToken: _mfaToken, factorsAllowed: factorsAllowed)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate empty factorsAllowed at the SDK boundary.

Line 61 documents a non-empty requirement, but the method forwards empty lists and fails later with invalid_request. Add a local ArgumentError guard for deterministic caller feedback.

Suggested fix
Future<List<MfaAuthenticator>> getAuthenticators(
-        {required final List<String> factorsAllowed}) =>
-    Auth0FlutterMfaPlatform.instance.getAuthenticators(_createApiRequest(
-        MfaGetAuthenticatorsOptions(
-            mfaToken: _mfaToken, factorsAllowed: factorsAllowed)));
+        {required final List<String> factorsAllowed}) {
+  if (factorsAllowed.isEmpty) {
+    throw ArgumentError.value(
+      factorsAllowed,
+      'factorsAllowed',
+      'Must contain at least one factor type.',
+    );
+  }
+  return Auth0FlutterMfaPlatform.instance.getAuthenticators(
+    _createApiRequest(
+      MfaGetAuthenticatorsOptions(
+        mfaToken: _mfaToken,
+        factorsAllowed: factorsAllowed,
+      ),
+    ),
+  );
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Future<List<MfaAuthenticator>> getAuthenticators(
{required final List<String> factorsAllowed}) =>
Auth0FlutterMfaPlatform.instance.getAuthenticators(_createApiRequest(
MfaGetAuthenticatorsOptions(
mfaToken: _mfaToken, factorsAllowed: factorsAllowed)));
Future<List<MfaAuthenticator>> getAuthenticators(
{required final List<String> factorsAllowed}) {
if (factorsAllowed.isEmpty) {
throw ArgumentError.value(
factorsAllowed,
'factorsAllowed',
'Must contain at least one factor type.',
);
}
return Auth0FlutterMfaPlatform.instance.getAuthenticators(
_createApiRequest(
MfaGetAuthenticatorsOptions(
mfaToken: _mfaToken,
factorsAllowed: factorsAllowed,
),
),
);
}
🤖 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/lib/src/mobile/mfa_api.dart` around lines 61 - 65, The
getAuthenticators method does not validate that the factorsAllowed parameter is
non-empty at the SDK boundary, causing failures later with generic
invalid_request errors. Add an ArgumentError validation at the beginning of the
getAuthenticators method to check if factorsAllowed is empty and throw an
ArgumentError with a clear message if it is, ensuring deterministic feedback to
the caller before making the platform call.

Comment on lines +91 to +95
test('enrollPhone forwards phoneNumber', () async {
when(mockedPlatform.enrollPhone(any))
.thenAnswer((final _) async => TestPlatform.enrollmentChallenge);

await mfa().enrollPhone(phoneNumber: '+1234567890');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Also assert mfaToken forwarding in this enrollPhone test.

Line 91 updates the API path, but this test only checks phoneNumber. Add an assertion for captured.options.mfaToken to prevent silent token-forwarding regressions.

Suggested test addition
       final captured = verify(mockedPlatform.enrollPhone(captureAny))
           .captured
           .single as ApiRequest<MfaEnrollPhoneOptions>;
+      expect(captured.options.mfaToken, 'mfa-token');
       expect(captured.options.phoneNumber, '+1234567890');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('enrollPhone forwards phoneNumber', () async {
when(mockedPlatform.enrollPhone(any))
.thenAnswer((final _) async => TestPlatform.enrollmentChallenge);
await mfa().enrollPhone(phoneNumber: '+1234567890');
test('enrollPhone forwards phoneNumber', () async {
when(mockedPlatform.enrollPhone(any))
.thenAnswer((final _) async => TestPlatform.enrollmentChallenge);
await mfa().enrollPhone(phoneNumber: '+1234567890');
final captured = verify(mockedPlatform.enrollPhone(captureAny))
.captured
.single as ApiRequest<MfaEnrollPhoneOptions>;
expect(captured.options.mfaToken, 'mfa-token');
expect(captured.options.phoneNumber, '+1234567890');
});
🤖 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/test/mobile/mfa_api_test.dart` around lines 91 - 95, The
enrollPhone test in the mfa_api_test.dart file currently only verifies that
phoneNumber is forwarded to the platform layer, but it should also assert that
mfaToken is being forwarded correctly. After the existing call to
mfa().enrollPhone(phoneNumber: '+1234567890'), add an assertion that captures
the arguments passed to mockedPlatform.enrollPhone and verifies that the
captured.options.mfaToken value is present and correctly forwarded to prevent
silent token-forwarding regressions.

/// bindingCode: '123456',
/// );
/// ```
Future<Credentials> verifyOob({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We need to have an option to pass scope and audience for methods returning Credentials. The bridge layer should pass the scope and audience to the corresponding native and SPA SDKs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added optional scopes and audience params to the three verify methods that return Credentials (verifyOtp, verifyOob, verifyRecoveryCode). They flow through MfaVerifyOptions and the bridge passes them down to each platform:

Android — appended to the verify token request via Request.addParameter("scope", …) / addParameter("audience", …)
iOS/macOS — chained onto the request with Request.parameters(...)
Web — passed to auth0-spa-js as scope (space-delimited) and audience on MfaVerifyParams
scopes is a Set joined with spaces to match the existing token-exchange convention (audience is only sent when non-null). Added unit tests on all four layers (platform-interface serialization, mobile, web, Android handler, plus an iOS smoke test). The methods that don't return Credentials (getAuthenticators, challenge, enroll*) were left untouched.

/// recoveryCode: 'ABCD1234...',
/// );
/// ```
Future<Credentials> verifyRecoveryCode(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

MfaException.toMfaMap() now includes the code and description from the exception (they carry the actual error), alongside the existing status code and error flags. This was pushed in an earlier commit.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
auth0_flutter/test/mobile/mfa_api_test.dart (1)

174-199: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover scopes/audience forwarding for verifyOob and verifyRecoveryCode too.

verifyOtp now asserts scope/audience forwarding, but the same new params on verifyOob and verifyRecoveryCode go untested. Add equivalent assertions to lock the contract on all three grant paths.

As per coding guidelines: "Check that new public symbols are covered by tests."

🤖 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/test/mobile/mfa_api_test.dart` around lines 174 - 199, The MFA
verify tests for verifyOob and verifyRecoveryCode are missing coverage for the
new scopes and audience parameters. Update the tests around mfa().verifyOob and
mfa().verifyRecoveryCode to pass these values and assert they are forwarded
through the captured ApiRequest<MfaVerifyOptions>, matching the existing
verifyOtp coverage so all three grant paths are locked down.

Source: Path instructions

♻️ Duplicate comments (3)
auth0_flutter_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart (2)

235-286: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add MethodChannel tests for enrollEmail and enrollPush paths.

This suite verifies several MFA methods, but it still misses direct invocation
coverage for the new public enrollEmail/enrollPush method-channel routes.

🤖 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_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart`
around lines 235 - 286, Add missing MethodChannel coverage for the new MFA
routes in the `MethodChannelAuth0FlutterMfa` test suite. Extend
`method_channel_auth0_flutter_mfa_test.dart` with tests that call
`platform.enrollEmail(...)` and `platform.enrollPush(...)`, then assert the
channel logs use `mfa#enrollEmail` and `mfa#enrollPush` and that the request
arguments are passed through correctly. Keep the new tests consistent with the
existing `enrollTotp`, `enrollPhone`, `challenge`, and `verify` cases.

Source: Path instructions


231-231: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace unchecked as Map casts in call-argument assertions.

These assertions cast log.single.arguments directly, which violates strict-cast
safety and can fail with non-map payloads.

Suggested fix
-      expect((log.single.arguments as Map)['factorsAllowed'], ['oob']);
+      final args =
+          Map<String, dynamic>.from(log.single.arguments as Map<Object?, Object?>);
+      expect(args['factorsAllowed'], ['oob']);
...
-      expect((log.single.arguments as Map)['phoneNumber'], '+123');
+      final args =
+          Map<String, dynamic>.from(log.single.arguments as Map<Object?, Object?>);
+      expect(args['phoneNumber'], '+123');
...
-      expect((log.single.arguments as Map)['grantType'], 'otp');
+      final args =
+          Map<String, dynamic>.from(log.single.arguments as Map<Object?, Object?>);
+      expect(args['grantType'], 'otp');

Also applies to: 253-253, 283-283

🤖 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_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart`
at line 231, The call-argument assertions in the MFA test rely on unchecked `as
Map` casts on log.single.arguments, which should be made strict-cast safe.
Update the assertions in method_channel_auth0_flutter_mfa_test so they first
verify the payload type before reading fields, and then access factorsAllowed
from the typed arguments instead of casting directly. Apply the same pattern to
the other similar assertions referenced in this test file to keep the log
argument checks consistent and safe.

Source: Path instructions

auth0_flutter_platform_interface/lib/src/mfa/mfa_verify_options.dart (1)

58-67: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Enforce grant-type invariants in MfaVerifyOptions constructor.

grantType selects a required credential field, but the constructor permits
invalid combinations (e.g., OTP without otp), so errors are deferred to native
handlers instead of failing fast at the API boundary.

Suggested fix
   MfaVerifyOptions({
     required this.mfaToken,
     required this.grantType,
     this.otp,
     this.oobCode,
     this.bindingCode,
     this.recoveryCode,
     this.scopes = const {},
     this.audience,
-  });
+  }) {
+    switch (grantType) {
+      case MfaVerifyGrantType.otp:
+        if (otp == null || otp!.isEmpty) {
+          throw ArgumentError.value(otp, 'otp', 'required for otp grant');
+        }
+      case MfaVerifyGrantType.oob:
+        if (oobCode == null || oobCode!.isEmpty) {
+          throw ArgumentError.value(
+              oobCode, 'oobCode', 'required for oob grant');
+        }
+      case MfaVerifyGrantType.recoveryCode:
+        if (recoveryCode == null || recoveryCode!.isEmpty) {
+          throw ArgumentError.value(
+              recoveryCode, 'recoveryCode', 'required for recovery_code grant');
+        }
+    }
+  }
🤖 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_platform_interface/lib/src/mfa/mfa_verify_options.dart` around
lines 58 - 67, `MfaVerifyOptions` currently allows mismatched `grantType` and
credential fields, so add constructor-time validation in `MfaVerifyOptions` to
fail fast when the required value for the selected grant is missing or when
incompatible fields are supplied. Update the named constructor to enforce the
invariant between `grantType` and the credential properties (`otp`, `oobCode`,
`bindingCode`, `recoveryCode`) by validating inputs immediately, and keep the
checks localized to `MfaVerifyOptions` so invalid combinations cannot be created
before reaching native handlers.

Source: Path instructions

🤖 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/mfa/VerifyRequestHandler.kt`:
- Around line 47-50: The `scopes` extraction in `VerifyRequestHandler` uses an
unsafe force-cast that can throw and skip the `result` callback; change it to a
safe cast with a fallback when `request.data["scopes"]` is missing or not an
`ArrayList`. Update the `scopes` handling so
`verifyRequest.addParameter("scope", ...)` only runs when the cast succeeds and
the list is non-empty, preserving the handler flow without crashing.

---

Outside diff comments:
In `@auth0_flutter/test/mobile/mfa_api_test.dart`:
- Around line 174-199: The MFA verify tests for verifyOob and verifyRecoveryCode
are missing coverage for the new scopes and audience parameters. Update the
tests around mfa().verifyOob and mfa().verifyRecoveryCode to pass these values
and assert they are forwarded through the captured ApiRequest<MfaVerifyOptions>,
matching the existing verifyOtp coverage so all three grant paths are locked
down.

---

Duplicate comments:
In `@auth0_flutter_platform_interface/lib/src/mfa/mfa_verify_options.dart`:
- Around line 58-67: `MfaVerifyOptions` currently allows mismatched `grantType`
and credential fields, so add constructor-time validation in `MfaVerifyOptions`
to fail fast when the required value for the selected grant is missing or when
incompatible fields are supplied. Update the named constructor to enforce the
invariant between `grantType` and the credential properties (`otp`, `oobCode`,
`bindingCode`, `recoveryCode`) by validating inputs immediately, and keep the
checks localized to `MfaVerifyOptions` so invalid combinations cannot be created
before reaching native handlers.

In
`@auth0_flutter_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart`:
- Around line 235-286: Add missing MethodChannel coverage for the new MFA routes
in the `MethodChannelAuth0FlutterMfa` test suite. Extend
`method_channel_auth0_flutter_mfa_test.dart` with tests that call
`platform.enrollEmail(...)` and `platform.enrollPush(...)`, then assert the
channel logs use `mfa#enrollEmail` and `mfa#enrollPush` and that the request
arguments are passed through correctly. Keep the new tests consistent with the
existing `enrollTotp`, `enrollPhone`, `challenge`, and `verify` cases.
- Line 231: The call-argument assertions in the MFA test rely on unchecked `as
Map` casts on log.single.arguments, which should be made strict-cast safe.
Update the assertions in method_channel_auth0_flutter_mfa_test so they first
verify the payload type before reading fields, and then access factorsAllowed
from the typed arguments instead of casting directly. Apply the same pattern to
the other similar assertions referenced in this test file to keep the log
argument checks consistent and safe.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b8dab182-423c-486f-8989-edcab2408950

📥 Commits

Reviewing files that changed from the base of the PR and between 99461eb and 2a8e159.

📒 Files selected for processing (11)
  • auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/VerifyRequestHandler.kt
  • auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/VerifyRequestHandlerTest.kt
  • auth0_flutter/darwin/Classes/MfaAPI/MfaVerifyMethodHandler.swift
  • auth0_flutter/lib/src/mobile/mfa_api.dart
  • auth0_flutter/lib/src/web/auth0_flutter_plugin_real.dart
  • auth0_flutter/lib/src/web/js_interop.dart
  • auth0_flutter/lib/src/web/mfa_web.dart
  • auth0_flutter/test/mobile/mfa_api_test.dart
  • auth0_flutter/test/web/mfa_web_test.dart
  • auth0_flutter_platform_interface/lib/src/mfa/mfa_verify_options.dart
  • auth0_flutter_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart

Comment on lines +47 to +50
val scopes = (request.data["scopes"] ?: arrayListOf<String>()) as ArrayList<*>
if (scopes.isNotEmpty()) {
verifyRequest.addParameter("scope", scopes.joinToString(separator = " "))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Force-cast on scopes can crash the handler.

as ArrayList<*> is an unchecked force-cast. If the MethodChannel delivers a non-ArrayList List (or any other type) for scopes, this throws ClassCastException and the result callback is never invoked — leaving the Dart Future hanging. Use a safe cast.

🛡️ Suggested safe cast
-        val scopes = (request.data["scopes"] ?: arrayListOf<String>()) as ArrayList<*>
-        if (scopes.isNotEmpty()) {
+        val scopes = request.data["scopes"] as? List<*> ?: emptyList<String>()
+        if (scopes.isNotEmpty()) {
             verifyRequest.addParameter("scope", scopes.joinToString(separator = " "))
         }

As per path instructions: "Avoid force-casts (as Type) — use safe casts (as? Type)... treat any unchecked cast as a bug."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val scopes = (request.data["scopes"] ?: arrayListOf<String>()) as ArrayList<*>
if (scopes.isNotEmpty()) {
verifyRequest.addParameter("scope", scopes.joinToString(separator = " "))
}
val scopes = request.data["scopes"] as? List<*> ?: emptyList<String>()
if (scopes.isNotEmpty()) {
verifyRequest.addParameter("scope", scopes.joinToString(separator = " "))
}
🤖 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/request_handlers/mfa/VerifyRequestHandler.kt`
around lines 47 - 50, The `scopes` extraction in `VerifyRequestHandler` uses an
unsafe force-cast that can throw and skip the `result` callback; change it to a
safe cast with a fallback when `request.data["scopes"]` is missing or not an
`ArrayList`. Update the `scopes` handling so
`verifyRequest.addParameter("scope", ...)` only runs when the cast succeeds and
the list is non-empty, preserving the handler flow without crashing.

Source: Path instructions

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.

2 participants