feat: Upgrade Auth0.Android to v4.0.0. - #918
Conversation
…26, compile/target 36, AGP 8.10.1, Kotlin 2.0.21, JDK 17, Gradle 8.11.1, Gson 2.11.0.
… v4 removed the constructors taking an Auth0 instance construct from AuthenticationAPIClient instead. clearCredentials() now full-wipes via Storage.removeAll()
v4 removed AuthenticationAPIClient.loginWithOTP/multifactorChallenge; reroute both through mfaClient. challenge() takes only authenticatorId (now required, types ignored)
v4 removed WebAuthProvider.useDPoP; DPoP is now configured per-request on the login builder.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (3)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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 |
…id v4 v4 renamed SSOCredentials.expiresIn (Int seconds) to expiresAt (Date). Convert back to seconds-until-expiry so the Dart model is unchanged, mirroring iOS (#909).
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/CredentialsManagerMethodCallHandler.kt`:
- Line 116: Replace the force-cast in the SecureCredentialsManager construction
flow with a safe cast to FragmentActivity. Validate the cast result before
constructing SecureCredentialsManager, and route a null activity through
result.error instead of allowing a ClassCastException.
In
`@auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/MultifactorChallengeApiRequestHandler.kt`:
- Around line 22-38: Replace throwing validation and unsafe String casts in
MultifactorChallengeApiRequestHandler and LoginWithOtpApiRequestHandler with
safe extraction of required values; when mfaToken, authenticatorId, or OTP
request values are missing or malformed, call result.error(...) exactly once and
return before creating the API request. Update
MultifactorChallengeApiRequestHandlerTest and LoginWithOtpApiRequestHandlerTest
to assert result.error(...) for missing properties and add malformed-type cases;
the listed LoginWithOtpApiRequestHandler.kt site requires the same handler
change, and its corresponding test requires the same assertion updates.
In
`@auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/SSOExchangeApiRequestHandlerTest.kt`:
- Around line 205-206: Make both SSO expiry tests deterministic by using a fixed
or captured clock when creating mock expiresAt values, then assert the exact
expected expiresIn delta instead of a timing window; update
auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/SSOExchangeApiRequestHandlerTest.kt
lines 205-206 and
auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/credentials_manager/GetSSOCredentialsRequestHandlerTest.kt
lines 138-141. In both assertions, replace the unsafe Long cast with a safe
numeric extraction that explicitly fails for missing or non-numeric expiresIn
values.
🪄 Autofix
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: 8d6df1ef-f34a-4f9f-8cb2-3d3f19c76f9f
⛔ Files ignored due to path filters (3)
auth0_flutter/example/android/app/build.gradleis excluded by!**/example/**auth0_flutter/example/android/gradle/wrapper/gradle-wrapper.propertiesis excluded by!**/example/**auth0_flutter/example/android/settings.gradleis excluded by!**/example/**
📒 Files selected for processing (13)
auth0_flutter/V3_MIGRATION_GUIDE.mdauth0_flutter/android/build.gradleauth0_flutter/android/gradle/wrapper/gradle-wrapper.propertiesauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/CredentialsManagerMethodCallHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/LoginWithOtpApiRequestHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/MultifactorChallengeApiRequestHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/SSOExchangeApiRequestHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/credentials_manager/GetSSOCredentialsRequestHandler.ktauth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/web_auth/LoginWebAuthRequestHandler.ktauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/LoginWithOtpApiRequestHandlerTest.ktauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/MultifactorChallengeApiRequestHandlerTest.ktauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/SSOExchangeApiRequestHandlerTest.ktauth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/credentials_manager/GetSSOCredentialsRequestHandlerTest.kt
| // v4 removed AuthenticationAPIClient.multifactorChallenge; the challenge | ||
| // now goes through MfaApiClient, whose challenge() takes only an | ||
| // authenticatorId (no challenge-type filtering). `authenticatorId` is | ||
| // therefore now required and any `types` value is ignored; the shared | ||
| // Dart surface is realigned separately in the integration PR (S-A). | ||
| assertHasProperties(listOf("mfaToken", "authenticatorId"), request.data) | ||
|
|
||
| val builder = api | ||
| .mfaClient(request.data["mfaToken"] as String) | ||
| .challenge(request.data["authenticatorId"] as String) | ||
|
|
||
| builder.start(object : Callback<Challenge, MfaChallengeException> { | ||
| override fun onFailure(exception: MfaChallengeException) { | ||
| result.error( | ||
| exception.getCode(), | ||
| exception.getDescription(), | ||
| exception.toMap() | ||
| exception.toMfaMap() |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Files:"
git ls-files 'auth0_flutter/android/src/main/kotlin/**' 'auth0_flutter/android/src/test/kotlin/**' | grep -E '(^|/)MultifactorChallengeApiRequestHandler\.kt$|(^|/)LoginWithOtpApiRequestHandler\.kt$|(^|/).*ApiRequestHandler.*Test\.kt$' || true
echo
echo "Outline related files:"
for f in auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/MultifactorChallengeApiRequestHandler.kt \
auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/LoginWithOtpApiRequestHandler.kt; do
if [ -f "$f" ]; then
echo "--- $f"
wc -l "$f"
ast-grep outline "$f" || true
fi
done
echo
echo "Relevant source excerpts:"
for f in auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/MultifactorChallengeApiRequestHandler.kt \
auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/LoginWithOtpApiRequestHandler.kt; do
if [ -f "$f" ]; then
echo "--- $f"
cat -n "$f"
fi
done
echo
echo "Relevant tests:"
for f in auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/MultifactorChallengeApiRequestHandlerTest.kt \
auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/LoginWithOtpApiRequestHandlerTest.kt; do
if [ -f "$f" ]; then
echo "--- $f"
cat -n "$f"
fi
done
echo
echo "Search assertHasProperties and Method call result handling"
rg -n "fun assertHasProperties|assertHasProperties|Result|result\.(success|error|notImplemented)|MethodCallRequest|MethodCall\.data" auth0_flutter/android/src main/kotlin auth0_flutter/android/src/test/kotlin 2>/dev/null || trueRepository: auth0/auth0-flutter
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Target files exist:"
for f in auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/MultifactorChallengeApiRequestHandler.kt \
auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/LoginWithOtpApiRequestHandler.kt \
auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/MultifactorChallengeApiRequestHandlerTest.kt
do
if [ -f "$f" ]; then
echo "$f"
wc -l "$f"
else
echo "MISSING: $f"
fi
done
echo
echo "Main handler excerpts:"
for f in auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/MultifactorChallengeApiRequestHandler.kt \
auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/LoginWithOtpApiRequestHandler.kt
do
if [ -f "$f" ]; then
echo "--- $f"
cat -n "$f"
fi
done
echo
echo "Multifactor test excerpt:"
f='auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/MultifactorChallengeApiRequestHandlerTest.kt'
if [ -f "$f" ]; then
sed -n '1,90p' "$f" | cat -n
fi
echo
echo "Login OTP test file if present:"
f='auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/LoginWithOtpApiRequestHandlerTest.kt'
if [ -f "$f" ]; then
cat -n "$f"
else
echo "MISSING: $f"
fi
echo
echo "assertHasProperties definition:"
rg -n "fun assertHasProperties|internal fun assertHasProperties|private fun assertHasProperties|assertHasProperties" auth0_flutter/android/src/main/kotlin auth0_flutter/android/src/test/kotlin | head -100Repository: auth0/auth0-flutter
Length of output: 32485
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "assertHas_PROPERTIES implementation:"
cat -n auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/utils/assertHasProperties.kt
echo
echo "MethodCallRequest definition:"
cat -n auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/MethodCallRequest.kt
echo
echo "Related existing validation/result pattern examples:"
rg -n -C 5 "as\\? String|as String|illegalArgument|result\\.error\\(|illegalArgument|require\\(" auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/utils | head -200Repository: auth0/auth0-flutter
Length of output: 33683
Return MethodChannel errors for invalid MFA request input.
assertHasProperties throws before result is used, and both handlers cast MethodChannel values with as String. A missing required value or wrong payload type should call result.error(...) once and return. Update the existing exception-only tests for MultifactorChallengeApiRequestHandler and LoginWithOtpApiRequestHandler to assert the result.error(...) behavior and add malformed-type coverage.
auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/MultifactorChallengeApiRequestHandler.kt: replace the throwing presence check andas Stringcasts with safe casts andresult.error(...)handling.auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/LoginWithOtpApiRequestHandler.kt: replace the throwing presence check andas Stringcasts with safe casts andresult.error(...)handling.auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/MultifactorChallengeApiRequestHandlerTest.ktandLoginWithOtpApiRequestHandlerTest.kt: replace missing-property exception tests withresult.error(...)assertions and add malformed-type coverage.
📍 Affects 3 files
auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/MultifactorChallengeApiRequestHandler.kt#L22-L38(this comment)auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/LoginWithOtpApiRequestHandler.kt#L29-L40auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/MultifactorChallengeApiRequestHandlerTest.kt#L43-L58
🤖 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/api/MultifactorChallengeApiRequestHandler.kt`
around lines 22 - 38, Replace throwing validation and unsafe String casts in
MultifactorChallengeApiRequestHandler and LoginWithOtpApiRequestHandler with
safe extraction of required values; when mfaToken, authenticatorId, or OTP
request values are missing or malformed, call result.error(...) exactly once and
return before creating the API request. Update
MultifactorChallengeApiRequestHandlerTest and LoginWithOtpApiRequestHandlerTest
to assert result.error(...) for missing properties and add malformed-type cases;
the listed LoginWithOtpApiRequestHandler.kt site requires the same handler
change, and its corresponding test requires the same assertion updates.
Source: Path instructions
There was a problem hiding this comment.
This is the established validation pattern (assertHasProperties + cast) used consistently across all handlers in the module. Changing it here would diverge from the rest of the codebase; reworking the validation approach is out of scope for the v4 bump.
There was a problem hiding this comment.
@utkrishtsahu, acknowledged. This PR should keep the established validation pattern.
The unchecked casts remain a MethodChannel crash risk, so I will keep the finding unresolved rather than request a local exception to the module convention. Would you like me to create a follow-up GitHub issue for a module-wide validation update?
✏️ Learnings added
Learnt from: utkrishtsahu
Repo: auth0/auth0-flutter PR: 918
File: auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers/api/MultifactorChallengeApiRequestHandler.kt:0-0
Timestamp: 2026-08-07T08:14:31.573Z
Learning: The Android request handlers in `auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/request_handlers` consistently use `assertHasProperties` followed by unchecked casts for MethodChannel request validation. Changes to this validation pattern should be handled as a module-wide refactor, not as part of an Auth0.Android v4 migration.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
You are interacting with an AI system.
| // Converted from an absolute Date, so allow a small execution-time delta. | ||
| assertThat((resultMap["expiresIn"] as Long) in 58L..60L, equalTo(true)) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Files:"
git ls-files | rg 'SSOExchangeApiRequestHandlerTest\.kt|GetSSOCredentialsRequestHandlerTest\.kt|auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers' || true
echo
echo "Test snippets:"
for f in \
auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/SSOExchangeApiRequestHandlerTest.kt \
auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/credentials_manager/GetSSOCredentialsRequestHandlerTest.kt
do
if [ -f "$f" ]; then
echo "--- $f"
wc -l "$f"
sed -n '160,225p' "$f"
echo
sed -n '110,155p' "$f"
echo
fi
done
echo
echo "Search expiresIn production test fixtures:"
rg -n 'expiresIn|System\.currentTimeMillis|Instant|Date\(|afterExpiresIn|beforeExpiresOn|expiresOn|expires in|TTL|ttl' auth0_flutter/android/src/test auth0_flutter/android/src/main || trueRepository: auth0/auth0-flutter
Length of output: 25112
Make the SSO expiry assertions deterministic and type-safe.
Both tests use System.currentTimeMillis() for the mock expiresAt and then assert a two-second window. Slow CI execution can fail correct conversions. Use a fixed/captured clock for the conversion, then assert an exact delta or deterministic token expiry. Also replace (resultMap["expiresIn"] as Long) with a safe cast/error path that rejects missing or non-numeric values.
📍 Affects 2 files
auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/SSOExchangeApiRequestHandlerTest.kt#L205-L206(this comment)auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/credentials_manager/GetSSOCredentialsRequestHandlerTest.kt#L138-L141
🤖 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/api/SSOExchangeApiRequestHandlerTest.kt`
around lines 205 - 206, Make both SSO expiry tests deterministic by using a
fixed or captured clock when creating mock expiresAt values, then assert the
exact expected expiresIn delta instead of a timing window; update
auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/SSOExchangeApiRequestHandlerTest.kt
lines 205-206 and
auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/credentials_manager/GetSSOCredentialsRequestHandlerTest.kt
lines 138-141. In both assertions, replace the unsafe Long cast with a safe
numeric extraction that explicitly fails for missing or non-numeric expiresIn
values.
Source: Path instructions
There was a problem hiding this comment.
Intentional: the handler derives expiresIn from expiresAt - now, so a small delta is expected. The range keeps the test robust without injecting a clock abstraction. Kept as-is.
There was a problem hiding this comment.
@utkrishtsahu Understood. The timing window matches the current expiresAt - now conversion contract and avoids adding a clock abstraction. I will leave this unchanged.
🐇
You are interacting with an AI system.
v4 made AuthenticationMethod and EnrollmentChallenge sealed classes, which Mockito cannot mock. Use concrete subclass instances in the affected success tests
| } | ||
|
|
||
| override fun onSuccess(credentials: SSOCredentials) { | ||
| // v4 renamed SSOCredentials.expiresIn (seconds) to expiresAt |
There was a problem hiding this comment.
Lets clean up all these AI generated comments.
|
|
||
| assertHasProperties(listOf("mfaToken", "otp"), args) | ||
|
|
||
| // v4 removed AuthenticationAPIClient.loginWithOTP; the inline MFA OTP |
There was a problem hiding this comment.
do we need these comments?
|
|
||
| builder.start(object : Callback<Challenge, AuthenticationException> { | ||
| override fun onFailure(exception: AuthenticationException) { | ||
| // v4 removed AuthenticationAPIClient.multifactorChallenge; the challenge |
📋 Changes
Upgrades the Android side of the SDK to Auth0.Android v4 and gets the native bridge compiling and passing against the new major. This covers tickets A-1, A-2, and A-3 on the v3 Android track, plus the compile-blocking pieces of A-4 and A-5 (targets
develop/v3.0).A-1 — Dependency bump & build floor (
android/build.gradle,example/android/*)com.auth0.android:auth03.21.0→4.0.0; Gson2.10.1→2.11.0minSdkVersion21→26,compileSdk/targetSdk34→368.4.0→8.10.1, Kotlin1.9.0→2.0.21, Java/jvmTarget8→17, Gradle wrapper8.7→8.11.1A-2 —
SecureCredentialsManagerconstructionAuth0instance; the manager is now built fromAuthenticationAPIClient(CredentialsManagerMethodCallHandler.kt). No Dart API change.A-3 — Inline MFA routed through
MfaApiClientAuthenticationAPIClient.loginWithOTP/multifactorChallenge.LoginWithOtpApiRequestHandlernow callsmfaClient(mfaToken).verify(Otp(otp));MultifactorChallengeApiRequestHandlernow callsmfaClient(mfaToken).challenge(authenticatorId). On AndroidauthenticatorIdis now required andtypesis ignored (matching iOS from feat: Upgrade Auth0.swift to v3.0.2 #909). No Dart signature change — the shared Dart surface is realigned later in S-A.A-4 —
SSOCredentials.expiresIn→expiresAt(compile-blocker pulled forward)SSOCredentials.expiresIn(Int, seconds) toexpiresAt(Date).SSOExchangeApiRequestHandlerandGetSSOCredentialsRequestHandlernow convert the absoluteDateback to seconds-until-expiry and keep sending it under theexpiresInkey, mirroring the iOS handler from feat: Upgrade Auth0.swift to v3.0.2 #909. No Dart model change — theexpiresAtrename on the shared Dart model lands in S-A.A-5 —
useDPoPmoved to the login builder (compile-blocker pulled forward)useDPoPoff the globalWebAuthProvideronto the login builder:WebAuthProvider.useDPoP(context)→builder.useDPoP(context)(LoginWebAuthRequestHandler.kt). Required for the module to compile; full per-request DPoP work remains A-5.Docs
auth0_flutter/V3_MIGRATION_GUIDE.md(living doc) covering the Android requirement bumps, theclearCredentialsfull-wipe behavior change, and themultifactorChallengeauthenticatorIdrequirement.Public API: unchanged in this PR. Management API, passkey providers, and dedicated MFA handlers required no changes — no removed-API references remained for them.
🎯 Testing
LoginWithOtpApiRequestHandlerTestandMultifactorChallengeApiRequestHandlerTestwere reworked to mock themfaClient().verify()/mfaClient().challenge()chain and the newMfaVerifyException/MfaChallengeExceptioncallbacks. Added amissing authenticatorIdtest for the challenge handler. Existing success/error/response-shape assertions are preserved.SSOExchangeApiRequestHandlerTestandGetSSOCredentialsRequestHandlerTestnow mockexpiresAt(aDate) and assert the converted seconds value.Summary by CodeRabbit
New Features
Documentation