Skip to content
Merged
113 changes: 113 additions & 0 deletions auth0_flutter/V3_MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Migration Guide from auth0_flutter v2 to v3

`auth0_flutter` v3 upgrades the wrapped native SDKs to their next major versions
β€” **Auth0.Android v4** on Android and **Auth0.swift v3** on iOS/macOS. Those
upgrades raise the minimum platform requirements and change some native
behavior that surfaces through the Dart API.

> **Note**
> This is a living document. It currently covers the **Android** track changes.
> The remaining cross-platform Dart API renames (for example
> `expiresIn` β†’ `expiresAt`, `clearSession` β†’ `logout`, `UserInfo` β†’
> `UserProfile`) land in later v3 PRs and will be documented here as they merge.

## Table of Contents

- [Requirements Changes](#requirements-changes)
- [Behavior Changes](#behavior-changes)
- [`credentialsManager.clearCredentials` now clears all stored data (Android)](#credentialsmanagerclearcredentials-now-clears-all-stored-data-android)
- [`api.multifactorChallenge` requires `authenticatorId` (Android)](#apimultifactorchallenge-requires-authenticatorid-android)
- [Getting Help](#getting-help)

## Requirements Changes

Because the underlying native SDKs raised their floors, `auth0_flutter` v3
raises the Android minimums:

| Requirement | v2 | v3 |
| --- | --- | --- |
| Android `minSdkVersion` | 21 | **26** (Android 8.0) |
| Android compile/target SDK | 34 | **36** |
| JDK (to build the Android module) | 8 | **17** |
| Kotlin | 1.9.x | **2.0.21** |
| Android Gradle Plugin | 8.4.x | **8.10.1** |
| Gradle | 8.7 | **8.11.1** |

**Migration:** if your app targets an Android `minSdkVersion` below 26, raise it
to at least 26 in your app's `android/app/build.gradle`. Ensure your build
environment uses JDK 17 (for example, set it in Android Studio under
*Settings β†’ Build, Execution, Deployment β†’ Build Tools β†’ Gradle β†’ Gradle JDK*,
or via `org.gradle.java.home`).

There are **no source changes required** to your Dart code for these
requirement bumps.

## Behavior Changes

### `credentialsManager.clearCredentials` now clears all stored data (Android)

**Change:** In Auth0.Android v4, `clearCredentials()` performs a full wipe of
the underlying storage (`Storage.removeAll()`) rather than removing only the
individual credential entries it wrote.

**Impact:** If your app stored unrelated values in the same
`SharedPreferences` instance that the credentials manager uses (for example, by
supplying a custom `sharedPreferencesName` via
`CredentialsManagerConfiguration` and reusing it elsewhere), calling
`clearCredentials` now removes those values too.

**Migration:** Do not share the credentials manager's storage with other data.
Keep any app data you need to persist independently of Auth0 credentials in a
separate store. The Dart API is unchanged:

```dart
// Same call as v2 β€” behavior on Android is now a full wipe of the store.
await credentialsManager.clearCredentials();
```

This affects Android only. iOS/macOS behavior is unchanged.

### `api.multifactorChallenge` requires `authenticatorId` (Android)

**Change:** Auth0.Android v4 removed the inline MFA methods from the
authentication client and routes MFA through a dedicated MFA client whose
`challenge` operation accepts only an `authenticatorId` β€” there is no
challenge-type filtering.

**Impact:** On Android, a `multifactorChallenge` call must now include
`authenticatorId`. A call that supplies only `types` and omits
`authenticatorId` β€” which was accepted in v2 β€” now fails on Android. Any `types`
value passed is ignored on Android.

> **Note**
> The Dart signature of `multifactorChallenge` is unchanged in this release
> (`types` and `authenticatorId` remain optional parameters), so this is a
> runtime behavior change on Android rather than a compile-time break. iOS
> already requires `authenticatorId`. A later v3 PR realigns the shared Dart
> API to make `authenticatorId` required and remove `types` across platforms.

**Migration:** Always pass `authenticatorId` when calling
`multifactorChallenge`, and stop relying on `types`:

```dart
// ❌ v2 β€” worked on Android, relied on challenge-type filtering
final challenge = await auth0.api.multifactorChallenge(
mfaToken: mfaToken,
types: [ChallengeType.otp, ChallengeType.oob],
);

// βœ… v3 β€” pass the authenticator to challenge
final challenge = await auth0.api.multifactorChallenge(
mfaToken: mfaToken,
authenticatorId: authenticatorId,
);
```

If you support one-time passwords and don't need to select a specific factor,
you can skip the challenge request and call `api.loginWithOtp` directly.

## Getting Help

If you encounter issues migrating, please open an issue on the
[auth0-flutter repository](https://github.com/auth0/auth0-flutter/issues) with
details of the API you're migrating and the platform affected.
18 changes: 9 additions & 9 deletions auth0_flutter/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
buildscript {
ext.kotlin_version = '1.9.0'
ext.kotlin_version = '2.0.21'
repositories {
google()
mavenCentral()
}

dependencies {
classpath "com.android.tools.build:gradle:8.4.0"
classpath "com.android.tools.build:gradle:8.10.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Expand All @@ -31,18 +31,18 @@ rootProject.allprojects {
}

android {
compileSdk 34
compileSdk 36

if (project.android.hasProperty("namespace")) {
namespace libApplicationId
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '17'
}

sourceSets {
Expand All @@ -52,7 +52,7 @@ android {
}

defaultConfig {
minSdkVersion 21
minSdkVersion 26
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders = [auth0Domain: "test-domain", auth0Scheme: "test"]
}
Expand All @@ -73,8 +73,8 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'com.auth0.android:auth0:3.21.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.auth0.android:auth0:4.0.0'
implementation 'com.google.code.gson:gson:2.11.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.hamcrest:java-hamcrest:2.0.0.0'
testImplementation "org.mockito.kotlin:mockito-kotlin:4.1.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import android.app.Activity
import android.content.Context
import androidx.annotation.NonNull
import androidx.fragment.app.FragmentActivity
import com.auth0.android.Auth0
import com.auth0.android.authentication.AuthenticationAPIClient
import com.auth0.android.authentication.storage.AuthenticationLevel
import com.auth0.android.authentication.storage.LocalAuthenticationOptions
Expand Down Expand Up @@ -114,11 +113,11 @@ class CredentialsManagerMethodCallHandler(private val requestHandlers: List<Cred

val newManager = if (options != null) {
SecureCredentialsManager(
apiClient, context, request.account, storage, activity as FragmentActivity, options
apiClient, context, storage, activity as FragmentActivity, options
Comment thread
coderabbitai[bot] marked this conversation as resolved.
)
} else {
SecureCredentialsManager(
apiClient, context, request.account, storage
apiClient, context, storage
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.auth0.auth0_flutter.request_handlers.api

import com.auth0.android.authentication.AuthenticationAPIClient
import com.auth0.android.authentication.AuthenticationException
import com.auth0.android.authentication.mfa.MfaException.MfaVerifyException
import com.auth0.android.authentication.mfa.MfaVerificationType
import com.auth0.android.callback.Callback
import com.auth0.android.result.Credentials
import com.auth0.auth0_flutter.request_handlers.MethodCallRequest
import com.auth0.auth0_flutter.toMap
import com.auth0.auth0_flutter.toMfaMap
import com.auth0.auth0_flutter.utils.assertHasProperties
import io.flutter.plugin.common.MethodChannel
import java.util.*
Expand All @@ -25,17 +27,15 @@ class LoginWithOtpApiRequestHandler: ApiRequestHandler {
assertHasProperties(listOf("mfaToken", "otp"), args)

val loginBuilder = api
.loginWithOTP(
args["mfaToken"] as String,
args["otp"] as String,
)
.mfaClient(args["mfaToken"] as String)
.verify(MfaVerificationType.Otp(args["otp"] as String))

loginBuilder.start(object : Callback<Credentials, AuthenticationException> {
override fun onFailure(exception: AuthenticationException) {
loginBuilder.start(object : Callback<Credentials, MfaVerifyException> {
override fun onFailure(exception: MfaVerifyException) {
result.error(
exception.getCode(),
exception.getDescription(),
exception.toMap()
exception.toMfaMap()
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.auth0.auth0_flutter.request_handlers.api

import com.auth0.android.authentication.AuthenticationAPIClient
import com.auth0.android.authentication.AuthenticationException
import com.auth0.android.authentication.mfa.MfaException.MfaChallengeException
import com.auth0.android.callback.Callback
import com.auth0.android.result.Challenge
import com.auth0.auth0_flutter.request_handlers.MethodCallRequest
import com.auth0.auth0_flutter.toMap
import com.auth0.auth0_flutter.toMfaMap
import com.auth0.auth0_flutter.utils.assertHasProperties
import io.flutter.plugin.common.MethodChannel

Expand All @@ -19,22 +19,18 @@ class MultifactorChallengeApiRequestHandler : ApiRequestHandler {
request: MethodCallRequest,
result: MethodChannel.Result
) {
assertHasProperties(listOf("mfaToken"), request.data)
assertHasProperties(listOf("mfaToken", "authenticatorId"), request.data)

val challengeTypes = request.data["types"] as ArrayList<*>?
val builder = api
.mfaClient(request.data["mfaToken"] as String)
.challenge(request.data["authenticatorId"] as String)

val builder = api.multifactorChallenge(
mfaToken = request.data["mfaToken"] as String,
challengeType = challengeTypes?.joinToString(separator = " "),
authenticatorId = request.data["authenticatorId"] as String?
)

builder.start(object : Callback<Challenge, AuthenticationException> {
override fun onFailure(exception: AuthenticationException) {
builder.start(object : Callback<Challenge, MfaChallengeException> {
override fun onFailure(exception: MfaChallengeException) {
result.error(
exception.getCode(),
exception.getDescription(),
exception.toMap()
exception.toMfaMap()
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ class SSOExchangeApiRequestHandler : ApiRequestHandler {
}

override fun onSuccess(credentials: SSOCredentials) {
val expiresIn =
(credentials.expiresAt.time - System.currentTimeMillis()) / 1000
val map = mutableMapOf<String, Any?>(
"sessionTransferToken" to credentials.sessionTransferToken,
"tokenType" to credentials.issuedTokenType,
"expiresIn" to credentials.expiresIn,
"expiresIn" to expiresIn,
"idToken" to credentials.idToken
)
credentials.refreshToken?.let { map["refreshToken"] = it }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ class GetSSOCredentialsRequestHandler : CredentialsManagerRequestHandler {
}

override fun onSuccess(credentials: SSOCredentials) {
val expiresIn =
(credentials.expiresAt.time - System.currentTimeMillis()) / 1000
val map = mutableMapOf<String, Any?>(
"sessionTransferToken" to credentials.sessionTransferToken,
"tokenType" to credentials.issuedTokenType,
"expiresIn" to credentials.expiresIn,
"expiresIn" to expiresIn,
"idToken" to credentials.idToken
)
credentials.refreshToken?.let { map["refreshToken"] = it }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class LoginWebAuthRequestHandler(
}

if (args["useDPoP"] == true) {
WebAuthProvider.useDPoP(context)
builder.useDPoP(context)
}

builder.start(context, object : Callback<Credentials, AuthenticationException> {
Expand Down
Loading
Loading