feat: Add Multi-Factor Authentication (MFA) support for mobile#872
feat: Add Multi-Factor Authentication (MFA) support for mobile#872utkrishtsahu wants to merge 9 commits into
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds 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. ChangesMFA Feature End-to-End
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 -->
|
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (19)
auth0_flutter/example/ios/Runner.xcodeproj/project.pbxprojis excluded by!**/example/**auth0_flutter/example/ios/Tests/Mfa/MfaChallengeMethodHandlerTests.swiftis excluded by!**/example/**auth0_flutter/example/ios/Tests/Mfa/MfaEnrollEmailMethodHandlerTests.swiftis excluded by!**/example/**auth0_flutter/example/ios/Tests/Mfa/MfaEnrollPhoneMethodHandlerTests.swiftis excluded by!**/example/**auth0_flutter/example/ios/Tests/Mfa/MfaEnrollPushMethodHandlerTests.swiftis excluded by!**/example/**auth0_flutter/example/ios/Tests/Mfa/MfaEnrollTotpMethodHandlerTests.swiftis excluded by!**/example/**auth0_flutter/example/ios/Tests/Mfa/MfaGetAuthenticatorsMethodHandlerTests.swiftis excluded by!**/example/**auth0_flutter/example/ios/Tests/Mfa/MfaSpies.swiftis excluded by!**/example/**auth0_flutter/example/ios/Tests/Mfa/MfaVerifyMethodHandlerTests.swiftis excluded by!**/example/**auth0_flutter/ios/Classes/MfaAPI/MfaChallengeMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaEnrollEmailMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaEnrollPhoneMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaEnrollPushMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaEnrollTotpMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaExtensions.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaGetAuthenticatorsMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaVerifyMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/test/mobile/mfa_api_test.mocks.dartis excluded by!**/*.mocks.dart
📒 Files selected for processing (46)
auth0_flutter/EXAMPLES.mdauth0_flutter/android/build.gradleauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/Auth0FlutterMfaMethodCallHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/Auth0FlutterPlugin.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/MfaExtensions.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/ChallengeRequestHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollEmailRequestHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollPhoneRequestHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollPushRequestHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollTotpRequestHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/GetAuthenticatorsRequestHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/MfaRequestHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/VerifyRequestHandler.ktauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/Auth0FlutterMfaMethodCallHandlerTest.ktauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/ChallengeRequestHandlerTest.ktauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollEmailRequestHandlerTest.ktauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollPhoneRequestHandlerTest.ktauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollPushRequestHandlerTest.ktauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/EnrollTotpRequestHandlerTest.ktauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/GetAuthenticatorsRequestHandlerTest.ktauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/VerifyRequestHandlerTest.ktauth0_flutter/darwin/Classes/SwiftAuth0FlutterPlugin.swiftauth0_flutter/darwin/auth0_flutter.podspecauth0_flutter/ios/auth0_flutter.podspecauth0_flutter/lib/auth0_flutter.dartauth0_flutter/lib/src/mobile/mfa_api.dartauth0_flutter/macos/auth0_flutter.podspecauth0_flutter/test/mobile/mfa_api_test.dartauth0_flutter_platform_interface/lib/auth0_flutter_platform_interface.dartauth0_flutter_platform_interface/lib/src/auth/api_exception.dartauth0_flutter_platform_interface/lib/src/mfa/auth0_flutter_mfa_platform.dartauth0_flutter_platform_interface/lib/src/mfa/method_channel_auth0_flutter_mfa.dartauth0_flutter_platform_interface/lib/src/mfa/mfa_authenticator.dartauth0_flutter_platform_interface/lib/src/mfa/mfa_challenge.dartauth0_flutter_platform_interface/lib/src/mfa/mfa_challenge_options.dartauth0_flutter_platform_interface/lib/src/mfa/mfa_enroll_email_options.dartauth0_flutter_platform_interface/lib/src/mfa/mfa_enroll_phone_options.dartauth0_flutter_platform_interface/lib/src/mfa/mfa_enroll_push_options.dartauth0_flutter_platform_interface/lib/src/mfa/mfa_enroll_totp_options.dartauth0_flutter_platform_interface/lib/src/mfa/mfa_enrollment_challenge.dartauth0_flutter_platform_interface/lib/src/mfa/mfa_exception.dartauth0_flutter_platform_interface/lib/src/mfa/mfa_get_authenticators_options.dartauth0_flutter_platform_interface/lib/src/mfa/mfa_requirements.dartauth0_flutter_platform_interface/lib/src/mfa/mfa_verify_options.dartauth0_flutter_platform_interface/test/api_exception_test.dartauth0_flutter_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart
| MfaRequirements? get mfaRequirements => | ||
| details['mfa_requirements'] is Map | ||
| ? MfaRequirements.fromMap( | ||
| Map<String, dynamic>.from(details['mfa_requirements'] as Map)) | ||
| : null; |
There was a problem hiding this comment.
🧩 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.dartRepository: auth0/auth0-flutter
Length of output: 607
🏁 Script executed:
find . -name "analysis_options.yaml" -o -name "analysis_options.yml" | head -5Repository: auth0/auth0-flutter
Length of output: 204
🏁 Script executed:
sed -n '40,55p' auth0_flutter_platform_interface/lib/src/auth/api_exception.dartRepository: 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 -50Repository: auth0/auth0-flutter
Length of output: 1559
🏁 Script executed:
cat auth0_flutter_platform_interface/lib/src/mfa/mfa_requirements.dartRepository: auth0/auth0-flutter
Length of output: 1655
🏁 Script executed:
rg -l "mfa_requirements" auth0_flutter_platform_interface/test/ | head -5Repository: 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/ -nRepository: auth0/auth0-flutter
Length of output: 732
🏁 Script executed:
cat auth0_flutter_platform_interface/test/api_exception_test.dartRepository: 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
| return result | ||
| .map((final item) => | ||
| MfaAuthenticator.fromMap(Map<String, dynamic>.from(item as Map))) | ||
| .toList(); |
There was a problem hiding this comment.
🧩 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.dartRepository: 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_interfaceRepository: auth0/auth0-flutter
Length of output: 118
🏁 Script executed:
rg -n "class MfaException|const.*unknown" auth0_flutter_platform_interface/lib/src/mfa/ -A 2Repository: 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.dartRepository: auth0/auth0-flutter
Length of output: 144
🏁 Script executed:
cat -n auth0_flutter_platform_interface/analysis_options.yamlRepository: auth0/auth0-flutter
Length of output: 3868
🏁 Script executed:
rg -n "fromMap" auth0_flutter_platform_interface/lib/src/mfa/mfa_authenticator.dart | head -5Repository: 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 10Repository: 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
| 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?, | ||
| ); |
There was a problem hiding this comment.
🧩 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.dartRepository: 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 2Repository: 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
| 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?, | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find and examine the MfaChallenge file
find . -type f -name "mfa_challenge.dart" | head -5Repository: 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 -nRepository: 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 -5Repository: 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/nullRepository: 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 | sortRepository: 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 -60Repository: 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 -20Repository: 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.
| 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
| 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?, | ||
| ); |
There was a problem hiding this comment.
🧩 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 -5Repository: 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 -80Repository: 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.yamlRepository: 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/nullRepository: 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 -20Repository: 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.yamlRepository: 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.dartRepository: 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}")
PYRepository: 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 -100Repository: 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
| @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) | ||
| } |
There was a problem hiding this comment.
🛠️ 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
| 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"))) | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| @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"))) | ||
| } |
There was a problem hiding this comment.
🛠️ 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
| 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); | ||
| }); |
There was a problem hiding this comment.
🧹 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
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (18)
auth0_flutter/ios/Classes/MfaAPI/MfaChallengeMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaEnrollEmailMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaEnrollPhoneMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaEnrollPushMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaEnrollTotpMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaExtensions.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaGetAuthenticatorsMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/MfaAPI/MfaVerifyMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/macos/Classes/MfaAPI/MfaChallengeMethodHandler.swiftis excluded by!auth0_flutter/macos/Classes/**auth0_flutter/macos/Classes/MfaAPI/MfaEnrollEmailMethodHandler.swiftis excluded by!auth0_flutter/macos/Classes/**auth0_flutter/macos/Classes/MfaAPI/MfaEnrollPhoneMethodHandler.swiftis excluded by!auth0_flutter/macos/Classes/**auth0_flutter/macos/Classes/MfaAPI/MfaEnrollPushMethodHandler.swiftis excluded by!auth0_flutter/macos/Classes/**auth0_flutter/macos/Classes/MfaAPI/MfaEnrollTotpMethodHandler.swiftis excluded by!auth0_flutter/macos/Classes/**auth0_flutter/macos/Classes/MfaAPI/MfaExtensions.swiftis excluded by!auth0_flutter/macos/Classes/**auth0_flutter/macos/Classes/MfaAPI/MfaGetAuthenticatorsMethodHandler.swiftis excluded by!auth0_flutter/macos/Classes/**auth0_flutter/macos/Classes/MfaAPI/MfaHandler.swiftis excluded by!auth0_flutter/macos/Classes/**auth0_flutter/macos/Classes/MfaAPI/MfaVerifyMethodHandler.swiftis excluded by!auth0_flutter/macos/Classes/**
📒 Files selected for processing (11)
auth0_flutter/EXAMPLES.mdauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/VerifyRequestHandlerTest.ktauth0_flutter/darwin/Classes/MfaAPI/MfaChallengeMethodHandler.swiftauth0_flutter/darwin/Classes/MfaAPI/MfaEnrollEmailMethodHandler.swiftauth0_flutter/darwin/Classes/MfaAPI/MfaEnrollPhoneMethodHandler.swiftauth0_flutter/darwin/Classes/MfaAPI/MfaEnrollPushMethodHandler.swiftauth0_flutter/darwin/Classes/MfaAPI/MfaEnrollTotpMethodHandler.swiftauth0_flutter/darwin/Classes/MfaAPI/MfaExtensions.swiftauth0_flutter/darwin/Classes/MfaAPI/MfaGetAuthenticatorsMethodHandler.swiftauth0_flutter/darwin/Classes/MfaAPI/MfaHandler.swiftauth0_flutter/darwin/Classes/MfaAPI/MfaVerifyMethodHandler.swift
| guard let phoneNumber = arguments["phoneNumber"] as? String else { | ||
| return callback(FlutterError(from: .requiredArgumentMissing("phoneNumber"))) | ||
| } | ||
|
|
||
| client | ||
| .enroll(mfaToken: mfaToken, phoneNumber: phoneNumber) | ||
| .start { |
There was a problem hiding this comment.
🎯 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.
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (2)
auth0_flutter/example/ios/Tests/Mfa/MfaGetAuthenticatorsMethodHandlerTests.swiftis excluded by!**/example/**auth0_flutter/example/web/index.htmlis excluded by!**/example/**
📒 Files selected for processing (10)
auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/Auth0FlutterPluginTest.ktauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/VerifyRequestHandlerTest.ktauth0_flutter/lib/auth0_flutter_web.dartauth0_flutter/lib/src/web/auth0_flutter_plugin_real.dartauth0_flutter/lib/src/web/auth0_flutter_web_platform_proxy.dartauth0_flutter/lib/src/web/extensions/mfa_extensions.dartauth0_flutter/lib/src/web/js_interop.dartauth0_flutter/lib/src/web/mfa_web.dartauth0_flutter/test/web/mfa_web_test.dartauth0_flutter_platform_interface/lib/src/auth0_flutter_web_platform.dart
| ) | ||
| } | ||
|
|
||
| fun MfaException.toMfaMap(): Map<String, Any> = buildMap { |
There was a problem hiding this comment.
All type of MFAExceptions has code and description too which is what actually carries the error . Add those two
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 winAdd tests for remaining new public MFA options/API paths.
Missing test coverage for:
MfaEnrollPushOptions.toMap()serializationMfaEnrollTotpOptions.toMap()serializationenrollEmailMethodChannel invocationenrollPushMethodChannel invocationAs 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 winReplace unsafe
as Mapcast with typed extraction usingisguard.The cast
(log.single.arguments as Map)at line 209 lacks a type guard. Withstrict-castsenabled, use anis Mapguard orMap<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
ischecks/pattern matching before casting; useasonly 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
📒 Files selected for processing (12)
auth0_flutter/EXAMPLES.mdauth0_flutter/README.mdauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/MfaExtensions.ktauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/MfaExtensionsTest.ktauth0_flutter/darwin/Classes/MfaAPI/MfaExtensions.swiftauth0_flutter/lib/src/mobile/mfa_api.dartauth0_flutter/lib/src/web/auth0_flutter_plugin_real.dartauth0_flutter/test/mobile/mfa_api_test.dartauth0_flutter_platform_interface/lib/src/mfa/mfa_enroll_phone_options.dartauth0_flutter_platform_interface/lib/src/mfa/mfa_get_authenticators_options.dartauth0_flutter_platform_interface/test/method_channel_auth0_flutter_mfa_test.dartauth0_flutter_platform_interface/test/mfa_exception_test.dart
| Future<List<MfaAuthenticator>> getAuthenticators( | ||
| {required final List<String> factorsAllowed}) => | ||
| Auth0FlutterMfaPlatform.instance.getAuthenticators(_createApiRequest( | ||
| MfaGetAuthenticatorsOptions( | ||
| mfaToken: _mfaToken, factorsAllowed: factorsAllowed))); |
There was a problem hiding this comment.
🎯 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.
| 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.
| test('enrollPhone forwards phoneNumber', () async { | ||
| when(mockedPlatform.enrollPhone(any)) | ||
| .thenAnswer((final _) async => TestPlatform.enrollmentChallenge); | ||
|
|
||
| await mfa().enrollPhone(phoneNumber: '+1234567890'); |
There was a problem hiding this comment.
📐 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.
| 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({ |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 winCover
scopes/audienceforwarding forverifyOobandverifyRecoveryCodetoo.
verifyOtpnow asserts scope/audience forwarding, but the same new params onverifyOobandverifyRecoveryCodego 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 winAdd MethodChannel tests for
enrollEmailandenrollPushpaths.This suite verifies several MFA methods, but it still misses direct invocation
coverage for the new publicenrollEmail/enrollPushmethod-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 winReplace unchecked
as Mapcasts in call-argument assertions.These assertions cast
log.single.argumentsdirectly, 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 winEnforce grant-type invariants in
MfaVerifyOptionsconstructor.
grantTypeselects a required credential field, but the constructor permits
invalid combinations (e.g., OTP withoutotp), 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
📒 Files selected for processing (11)
auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/VerifyRequestHandler.ktauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/mfa/VerifyRequestHandlerTest.ktauth0_flutter/darwin/Classes/MfaAPI/MfaVerifyMethodHandler.swiftauth0_flutter/lib/src/mobile/mfa_api.dartauth0_flutter/lib/src/web/auth0_flutter_plugin_real.dartauth0_flutter/lib/src/web/js_interop.dartauth0_flutter/lib/src/web/mfa_web.dartauth0_flutter/test/mobile/mfa_api_test.dartauth0_flutter/test/web/mfa_web_test.dartauth0_flutter_platform_interface/lib/src/mfa/mfa_verify_options.dartauth0_flutter_platform_interface/test/method_channel_auth0_flutter_mfa_test.dart
| val scopes = (request.data["scopes"] ?: arrayListOf<String>()) as ArrayList<*> | ||
| if (scopes.isNotEmpty()) { | ||
| verifyRequest.addParameter("scope", scopes.joinToString(separator = " ")) | ||
| } |
There was a problem hiding this comment.
🩺 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.
| 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
📋 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_requirederror during authentication — enrolling new factors, challenging existing ones, and exchanging a verification code for credentials.New public API —
auth0.mfa(MfaApi):getAuthenticators(...)enrollTotp(...)enrollPhone(...)enrollEmail(...)enrollPush(...)challenge(...)verify(...)otp,oob, orrecoveryCodegrant and returnsCredentialsAll methods take the
mfaTokenfrom amfa_requiredauthentication error.All methods take the
mfaTokenfrom amfa_requiredauthentication error.Platform interface (
auth0_flutter_platform_interface):auth0.com/auth0_flutter/mfa) and platform contract.MfaAuthenticator,MfaChallenge,MfaEnrollmentChallenge,MfaRequirements, theMfa*Optionsrequest types, andMfaException.mfa_requiredchallenge details are now surfaced onApiException.Native implementations:
MfaAPI/method handlers built on theMFAClientAPI. Bumps Auth0.swift2.21.2 → 2.22.0(all three podspecs).request_handlers/mfa/request handlers onMfaApiClient. Bumpscom.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:
MfaApi(mfa_api_test.dart), the method channel (method_channel_auth0_flutter_mfa_test.dart), andmfa_requiredparsing onApiException(api_exception_test.dart).request_handlers/mfa/*Test.kt).example/ios/Tests/Mfa/*).Summary by CodeRabbit
Auth0.mfa(mfaToken)andAuth0Web.mfa(mfaToken)entry points, with strongly typed MFA options/models and structured MFA exceptions.mfa_tokenflow) and updated README with MFA links.2.22.0.