feat: add passwordless OTP on database connections#886
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
WalkthroughThis PR adds passwordless OTP support for database connections across the Flutter platform interface, native Android and Apple handlers, the Dart API, tests, and examples. It also threads ChangesPasswordless OTP Feature
Estimated code review effort: 4 (Complex) | ~60 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
auth0_flutter_platform_interface/lib/src/auth/auth_passwordless_login_with_otp_options.dart (1)
1-24: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winEnsure
openidis always included in passwordless OTP scopes.
loginWithOtp()promisesopenid“regardless of this setting,” but this options object forwardsscopesverbatim, and the native handlers only add the default scope when the set is empty. If a caller passes custom scopes withoutopenid, the auth request can miss the ID token flow.🤖 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/auth_passwordless_login_with_otp_options.dart` around lines 1 - 24, The AuthPasswordlessLoginWithOtpOptions scope mapping forwards custom scopes as-is, so openid can be omitted when callers provide non-empty scopes. Update AuthPasswordlessLoginWithOtpOptions.toMap() to always include openid in the scopes list before sending it to native handlers, while preserving any additional scopes and leaving loginWithOtp() behavior consistent with its promise.
🤖 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/test/method_channel_auth0_flutter_auth_test.dart`:
- Around line 1826-1900: Add a missing test in the passwordlessLoginWithOtp
group to verify default omission behavior for optional fields. Use
MethodChannelAuth0FlutterAuth.passwordlessLoginWithOtp with only required
inputs, then assert the captured MethodCall arguments omit audience and send
scopes as empty/default, matching the existing patterns in the login, renew, and
customTokenExchange tests. This will cover the native contract for absent
optional values without changing the implementation.
- Around line 1658-1730: Add a missing test in the
passwordlessChallengeWithEmail group to explicitly verify the default value of
allowSignup when it is omitted, matching the coverage already present in
passwordlessChallengeWithPhoneNumber. In
MethodChannelAuth0FlutterAuth.passwordlessChallengeWithEmail, construct an
AuthPasswordlessChallengeEmailOptions without allowSignup, invoke the method,
and assert the captured MethodCall arguments include allowSignup as false. This
keeps the behavior for the new public symbol pinned down and consistent with the
sibling challenge tests.
In
`@auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasswordlessChallengeWithEmailApiRequestHandler.kt`:
- Around line 26-27: The
`PasswordlessChallengeWithEmailApiRequestHandler.handle` path is using unsafe
force casts for `args["email"]` and `args["connection"]`, which can crash on
malformed MethodChannel input. Replace these casts with safe type handling in
this handler, and explicitly validate/null-check the values before proceeding so
invalid args return a clean Flutter error instead of throwing
`ClassCastException`.
In
`@auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasswordlessChallengeWithPhoneNumberApiRequestHandler.kt`:
- Around line 28-29: The PasswordlessChallengeWithPhoneNumberApiRequestHandler
request parsing still uses unchecked force-casts for args["phoneNumber"] and
args["connection"], which can crash on malformed input; update the handler to
use safe casts and explicit null/failure handling, matching the pattern used in
the email handler, so invalid arguments return a Flutter error instead of
throwing. Focus on the argument extraction in
PasswordlessChallengeWithPhoneNumberApiRequestHandler and ensure both
phoneNumber and connection are validated before proceeding.
In
`@auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasswordlessLoginWithOtpApiRequestHandler.kt`:
- Around line 25-28: The PasswordlessLoginWithOtpApiRequestHandler handler is
force-casting `authSession` and `otp` to String after only checking presence
with `assertHasProperties`, which can crash the MethodChannel on bad input.
Update the request parsing in `PasswordlessLoginWithOtpApiRequestHandler` to use
safe casts for both values, validate that each is a non-null String, and return
a clean `result.error` when either value is missing or of the wrong type instead
of throwing.
In
`@auth0_flutter/darwin/Classes/AuthAPI/AuthAPIPasswordlessLoginWithOtpMethodHandler.swift`:
- Around line 27-33: The `PasswordlessChallenge` reconstruction in
`AuthAPIPasswordlessLoginWithOtpMethodHandler` should not rely on a JSON
round-trip through the internal `auth_session` coding key. Update the decoding
fallback to avoid depending on `JSONSerialization`/`JSONDecoder` for this type,
using a safer reconstruction path tied to `authSession` if available. If
reconstruction still fails, return a dedicated `FlutterError` for challenge
reconstruction instead of `Argument.authSession` missing, so the failure clearly
matches the `callback` path here.
In `@auth0_flutter/EXAMPLES.md`:
- Line 1326: The `loginWithOtp` note in `EXAMPLES.md` points to a broken
`#api-client-errors` fragment, so update the markdown link to the existing API
error-handling heading slug in this document. Find the matching heading near the
`ApiException`/error section and change the anchor in the MFA paragraph so the
`loginWithOtp` reference resolves correctly.
---
Outside diff comments:
In
`@auth0_flutter_platform_interface/lib/src/auth/auth_passwordless_login_with_otp_options.dart`:
- Around line 1-24: The AuthPasswordlessLoginWithOtpOptions scope mapping
forwards custom scopes as-is, so openid can be omitted when callers provide
non-empty scopes. Update AuthPasswordlessLoginWithOtpOptions.toMap() to always
include openid in the scopes list before sending it to native handlers, while
preserving any additional scopes and leaving loginWithOtp() behavior consistent
with its promise.
🪄 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: 595daa70-6cde-4d38-aaa1-d196471b9c8a
⛔ Files ignored due to path filters (7)
auth0_flutter/ios/Classes/AuthAPI/AuthAPIPasswordlessChallengeEmailMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/AuthAPI/AuthAPIPasswordlessChallengePhoneNumberMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/ios/Classes/AuthAPI/AuthAPIPasswordlessLoginWithOtpMethodHandler.swiftis excluded by!auth0_flutter/ios/Classes/**auth0_flutter/macos/Classes/AuthAPI/AuthAPIPasswordlessChallengeEmailMethodHandler.swiftis excluded by!auth0_flutter/macos/Classes/**auth0_flutter/macos/Classes/AuthAPI/AuthAPIPasswordlessChallengePhoneNumberMethodHandler.swiftis excluded by!auth0_flutter/macos/Classes/**auth0_flutter/macos/Classes/AuthAPI/AuthAPIPasswordlessLoginWithOtpMethodHandler.swiftis excluded by!auth0_flutter/macos/Classes/**auth0_flutter/test/mobile/passwordless_api_test.mocks.dartis excluded by!**/*.mocks.dart
📒 Files selected for processing (23)
auth0_flutter/EXAMPLES.mdauth0_flutter/android/build.gradleauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/Auth0FlutterPlugin.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasswordlessChallengeWithEmailApiRequestHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasswordlessChallengeWithPhoneNumberApiRequestHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasswordlessLoginWithOtpApiRequestHandler.ktauth0_flutter/darwin/Classes/AuthAPI/AuthAPIHandler.swiftauth0_flutter/darwin/Classes/AuthAPI/AuthAPIPasswordlessChallengeEmailMethodHandler.swiftauth0_flutter/darwin/Classes/AuthAPI/AuthAPIPasswordlessChallengePhoneNumberMethodHandler.swiftauth0_flutter/darwin/Classes/AuthAPI/AuthAPIPasswordlessLoginWithOtpMethodHandler.swiftauth0_flutter/ios/auth0_flutter.podspecauth0_flutter/lib/auth0_flutter.dartauth0_flutter/lib/src/mobile/passwordless_api.dartauth0_flutter/test/mobile/passwordless_api_test.dartauth0_flutter_platform_interface/lib/auth0_flutter_platform_interface.dartauth0_flutter_platform_interface/lib/src/auth/auth_passwordless_challenge_email_options.dartauth0_flutter_platform_interface/lib/src/auth/auth_passwordless_challenge_phone_options.dartauth0_flutter_platform_interface/lib/src/auth/auth_passwordless_login_with_otp_options.dartauth0_flutter_platform_interface/lib/src/auth/delivery_method.dartauth0_flutter_platform_interface/lib/src/auth/passwordless_challenge.dartauth0_flutter_platform_interface/lib/src/auth0_flutter_auth_platform.dartauth0_flutter_platform_interface/lib/src/method_channel_auth0_flutter_auth.dartauth0_flutter_platform_interface/test/method_channel_auth0_flutter_auth_test.dart
| // `PasswordlessChallenge` has no public initializer, but it is `Codable` | ||
| // with `authSession` mapped to the `auth_session` JSON key. Reconstruct | ||
| // it by decoding the opaque session token. | ||
| guard let challengeData = try? JSONSerialization.data(withJSONObject: ["auth_session": authSession]), | ||
| let challenge = try? JSONDecoder().decode(PasswordlessChallenge.self, from: challengeData) else { | ||
| return callback(FlutterError(from: .requiredArgumentMissing(Argument.authSession.rawValue))) | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🌐 Web query:
Does Auth0.swift 2.23.0 provide a public way to construct PasswordlessChallenge from an authSession string?
💡 Result:
No, Auth0.swift 2.23.0 does not provide a public way to construct a PasswordlessChallenge from an authSession string. In the Auth0.swift SDK, passwordless authentication is handled by the Authentication client using methods such as startPasswordless(email:type:connection:) and login(email:code:...), which manage the flow internally without requiring manual construction of challenge objects from session strings [1][2][3]. The confusion may arise from other passwordless-related SDKs (such as the Bitwarden-maintained 'Passwordless' library) which use different terminology and internal structures [4][5], or from misunderstanding the Auth0 passkey implementation, which handles challenge data internally when using the SDK's native passkey login methods [6]. If you are working with Auth0 passkeys, the SDK automatically manages the challenge and WebAuthn responses when you use the provided passkey login overloads [6].
Citations:
- 1: https://github.com/auth0/Auth0.swift/blob/master/Auth0/Authentication.swift
- 2: https://auth0.github.io/Auth0.swift/documentation/auth0/authentication/
- 3: https://deepwiki.com/auth0/Auth0.swift/4.3-passwordless-authentication
- 4: https://cocoapods.org/pods/Passwordless
- 5: https://github.com/bitwarden/passwordless-ios
- 6: https://auth0.com/blog/sign-up-log-in-passkeys-ios-using-auth0-native-login/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
git ls-files | rg 'auth0_flutter/darwin/Classes/AuthAPI/AuthAPIPasswordlessLoginWithOtpMethodHandler.swift|PasswordlessChallenge|Auth0\.swift|Podfile|Podfile.lock|Package\.resolved|pubspec\.yaml'
printf '\n--- file outline ---\n'
ast-grep outline auth0_flutter/darwin/Classes/AuthAPI/AuthAPIPasswordlessLoginWithOtpMethodHandler.swift --view expanded || true
printf '\n--- relevant search ---\n'
rg -n "PasswordlessChallenge|auth_session|requiredArgumentMissing\\(Argument\\.authSession" auth0_flutter/darwin -SRepository: auth0/auth0-flutter
Length of output: 4393
Avoid the JSON round-trip for PasswordlessChallenge. Auth0.swift 2.23.0 still doesn’t expose a public initializer from authSession, so this depends on the internal auth_session coding key and can break on SDK updates. If decoding fails, return a reconstruction-specific FlutterError; .requiredArgumentMissing(authSession) points to the wrong failure.
🤖 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/AuthAPI/AuthAPIPasswordlessLoginWithOtpMethodHandler.swift`
around lines 27 - 33, The `PasswordlessChallenge` reconstruction in
`AuthAPIPasswordlessLoginWithOtpMethodHandler` should not rely on a JSON
round-trip through the internal `auth_session` coding key. Update the decoding
fallback to avoid depending on `JSONSerialization`/`JSONDecoder` for this type,
using a safer reconstruction path tied to `authSession` if available. If
reconstruction still fails, return a dedicated `FlutterError` for challenge
reconstruction instead of `Argument.authSession` missing, so the failure clearly
matches the `callback` path here.
| .challengeWithEmail(email, connection, allowSignup) | ||
| .start(object : Callback<PasswordlessChallenge, AuthenticationException> { | ||
| override fun onFailure(exception: AuthenticationException) { | ||
| result.error( |
There was a problem hiding this comment.
Do you want to handle the no_network scenario in the error cases ?
There was a problem hiding this comment.
Network errors are already surfaced through the existing failure path: the native SDKs raise them as AuthenticationException/AuthenticationError, which flow into the onFailure/.failure branch and reach Dart as an ApiException (with isNetworkError/retryable metadata intact). No special-casing needed here — it's handled the same way as every other API call in the SDK.
| > Sending additional parameters is supported only on iOS at the moment. | ||
|
|
||
| ### Passwordless OTP on database connections | ||
|
|
There was a problem hiding this comment.
add a note mentioning this is currently in EA
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/auth0_flutter.podspec`:
- Line 22: The Auth0 dependency pin in auth0_flutter.podspec references an
unavailable pod version, so update the s.dependency entry for Auth0 to a
published stable 2.x release. Use the existing podspec dependency declaration as
the fix point and change the version from the current pin to a version that is
actually available, such as 2.22.0, so pod install and CI can resolve it.
🪄 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: ddb71fae-d7ea-43cf-8d1e-11e2b77c2f1b
📒 Files selected for processing (9)
auth0_flutter/EXAMPLES.mdauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasswordlessChallengeWithEmailApiRequestHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasswordlessChallengeWithPhoneNumberApiRequestHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasswordlessLoginWithOtpApiRequestHandler.ktauth0_flutter/darwin/Classes/AuthAPI/AuthAPIPasswordlessLoginWithOtpMethodHandler.swiftauth0_flutter/darwin/auth0_flutter.podspecauth0_flutter/lib/src/mobile/passwordless_api.dartauth0_flutter/macos/auth0_flutter.podspecauth0_flutter_platform_interface/test/method_channel_auth0_flutter_auth_test.dart
| s.osx.dependency 'FlutterMacOS' | ||
|
|
||
| s.dependency 'Auth0', '2.21.2' | ||
| s.dependency 'Auth0', '2.23.0' |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Is Auth0.swift version 2.23.0 published on CocoaPods or GitHub releases?
💡 Result:
Auth0.swift version 2.23.0 has not been published on either CocoaPods or GitHub Releases. As of July 3, 2026, the available release history for the repository shows that version 2.22.0 is the latest stable release in the 2.x line, followed by pre-release versions such as 3.0.0-beta.1 and 3.0.0-beta.2 [1][2][3]. The repository's changelog and release history skip version 2.23.0 [1][4].
Citations:
- 1: https://github.com/auth0/Auth0.swift/releases
- 2: https://github.com/auth0/Auth0.swift/releases/tag/2.22.0
- 3: https://github.com/auth0/Auth0.swift/releases/tag/3.0.0-beta.2
- 4: https://github.com/auth0/Auth0.swift/blob/3.0.0-beta.2/CHANGELOG.md
Pin a published Auth0 pod version
Auth0 2.23.0 doesn’t appear to be published; the latest stable 2.x release is 2.22.0. This pin will break pod install/CI until it’s changed to an available version.
🤖 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/auth0_flutter.podspec` at line 22, The Auth0 dependency
pin in auth0_flutter.podspec references an unavailable pod version, so update
the s.dependency entry for Auth0 to a published stable 2.x release. Use the
existing podspec dependency declaration as the fix point and change the version
from the current pin to a version that is actually available, such as 2.22.0, so
pod install and CI can resolve it.
📋 Changes
📋 Changes
Adds support for the embedded Passwordless OTP flow on Auth0 database connections (connections configured with
email_otp/phone_otp). Unlike the existing passwordless methods onauth0.api— which target dedicatedemail/smsstrategy connections — this is a challenge/response flow against standardauth0-strategy database connections, exposed through a new dedicatedauth0.passwordlessclient.New public API (
auth0.passwordless)challengeWithEmail({ email, connection, allowSignup = false })→PasswordlessChallengechallengeWithPhoneNumber({ phoneNumber, connection, deliveryMethod = DeliveryMethod.text, allowSignup = false })→PasswordlessChallengeloginWithOtp({ authSession, otp, scopes, audience })→CredentialsUsage:
Types added
Types added
Native layer
Android: bumped com.auth0.android:auth0 3.19.0 → 3.20.0; added three request handlers backed by passwordlessClient().challengeWithEmail / challengeWithPhoneNumber / loginWithOTP, registered in the plugin.
iOS / macOS: bumped Auth0 2.21.2 → 2.23.0; added three method handlers backed by passwordlessChallenge(...) and login(otp:challenge:audience:scope:). Handlers live in darwin/Classes with ios/macos symlinks per the repo convention.
Behavior notes
📎 References
🎯 Testing
Automated
Platform interface — auth0_flutter_platform_interface/test/method_channel_auth0_flutter_auth_test.dart: added groups for passwordlessChallengeWithEmail, passwordlessChallengeWithPhoneNumber, and passwordlessLoginWithOtp verifying the correct method-channel method is invoked, all properties are marshalled correctly, defaults are applied (allowSignup: false, deliveryMethod: text), the parsed result is returned, and a PlatformException maps to an ApiException.
Public API — auth0_flutter/test/mobile/passwordless_api_test.dart: verifies each auth0.passwordless method passes options through to the platform, applies defaults, returns the platform result, and that useDPoP from the Auth0 constructor is threaded into the request.
Summary by CodeRabbit
allowSignup, phone delivery (text/voice),scopes,audience, anduseDPoPfor DPoP-bound exchange.