diff --git a/.github/actions/setup-darwin/action.yml b/.github/actions/setup-darwin/action.yml
index cb2c90193..84d523dab 100644
--- a/.github/actions/setup-darwin/action.yml
+++ b/.github/actions/setup-darwin/action.yml
@@ -79,7 +79,7 @@ runs:
echo "::error::FlutterGeneratedPluginSwiftPackage/Package.swift not found at expected path"
exit 1
fi
- sed -i '' 's/\.iOS(".*")/.iOS("14.0")/g' "$PACKAGE_FILE"
+ sed -i '' 's/\.iOS(".*")/.iOS("15.0")/g' "$PACKAGE_FILE"
shell: bash
- name: Fix macOS minimum deployment target for SPM
@@ -90,7 +90,7 @@ runs:
echo "::error::FlutterGeneratedPluginSwiftPackage/Package.swift not found at expected path"
exit 1
fi
- sed -i '' 's/\.macOS(".*")/.macOS("11.0")/g' "$PACKAGE_FILE"
+ sed -i '' 's/\.macOS(".*")/.macOS("12.0")/g' "$PACKAGE_FILE"
shell: bash
- name: Set .env
diff --git a/CLAUDE.md b/CLAUDE.md
index f7571bd4b..93963b683 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -26,7 +26,7 @@ Apply these on every task in this repo — they keep changes correct, small, and
- **Language:** Dart 3.5.0+ (Flutter 3.24.0+), plus native Kotlin (Android), Swift (iOS/macOS via SPM/CocoaPods), and C++ (Windows)
- **Tech Stack:** Federated Flutter plugin — a Dart-facing package (`auth0_flutter`) and a platform-interface package (`auth0_flutter_platform_interface`) that dispatches to native implementations over `MethodChannel`; web uses `dart:js_interop` over `auth0-spa-js`
- **Package Manager:** `pub` (Dart/Flutter); native deps via CocoaPods/SPM (Apple), Gradle (Android), vcpkg (Windows)
-- **Minimum Platform Version:** Android API 21+, iOS 14+, macOS 11+, Windows 10+
+- **Minimum Platform Version:** Android API 26+, iOS 15+, macOS 12+, Windows 10+
- **Dependencies:** `auth0_flutter_platform_interface`, `plugin_platform_interface`, `web` · native: SimpleKeychain 1.3.0 (Apple) · test: `mockito`, `flutter_test`, `dart_jsonwebtoken` — see `auth0_flutter/pubspec.yaml` and `auth0_flutter_platform_interface/pubspec.yaml` for the full list
---
diff --git a/auth0_flutter/README.md b/auth0_flutter/README.md
index 1a95de24c..ae3d456eb 100644
--- a/auth0_flutter/README.md
+++ b/auth0_flutter/README.md
@@ -30,7 +30,7 @@
| Flutter | Android | iOS | macOS | Windows |
| :---------- | :-------------- | :---------------- | :---------------- | :------------------------------- |
-| SDK 3.24.0+ | Android API 26+ | iOS 14+ | macOS 11+ | Windows 10+ |
+| SDK 3.24.0+ | Android API 26+ | iOS 15+ | macOS 12+ | Windows 10+ |
| Dart 3.5.0+ | Java 17+ | Swift 5.9+ | Swift 5.9+ | C++ 17, Visual Studio 2022 |
| | | Xcode 15.x / 16.x | Xcode 15.x / 16.x | vcpkg (for dependencies) |
@@ -370,7 +370,7 @@ final auth0Web = Auth0Web('YOUR_AUTH0_DOMAIN', 'YOUR_AUTH0_CLIENT_ID');
Finally, in your `index.html` add the following `
+
```
> ⚠️ If you use [Custom Token Exchange](EXAMPLES.md#custom-token-exchange) with delegation/impersonation (an actor token), you must reference `auth0-spa-js` **2.20.0 or later**. Actor-token support was added to the underlying SPA SDK in [2.20.0](https://github.com/auth0/auth0-spa-js/releases/tag/v2.20.0); older versions silently drop the actor token, so the returned ID token has no `act` claim and `credentials.user.actor` is `null`.
diff --git a/auth0_flutter/V3_MIGRATION_GUIDE.md b/auth0_flutter/V3_MIGRATION_GUIDE.md
index d2e0c0d9e..8ea82c730 100644
--- a/auth0_flutter/V3_MIGRATION_GUIDE.md
+++ b/auth0_flutter/V3_MIGRATION_GUIDE.md
@@ -31,10 +31,12 @@ from v2 to v3.
## Requirements Changes
Because the underlying native SDKs raised their floors, `auth0_flutter` v3
-raises the Android minimums:
+raises the Android, iOS, and macOS minimums:
| Requirement | v2 | v3 |
| --- | --- | --- |
+| iOS deployment target | 14.0 | **15.0** |
+| macOS deployment target | 11.0 | **12.0** |
| Android `minSdkVersion` | 21 | **26** (Android 8.0) |
| Android compile/target SDK | 34 | **36** |
| JDK (to build the Android module) | 8 | **17** |
@@ -42,7 +44,13 @@ raises the Android minimums:
| 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
+**Migration (iOS/macOS):** if your app's deployment target is below iOS 15.0 or
+macOS 12.0, raise it. Update the `platform` line in `ios/Podfile` (for example
+`platform :ios, '15.0'`) and `macos/Podfile` (`platform :osx, '12.0'`), and the
+deployment target of the Runner target in Xcode. These floors come from
+Auth0.swift v3.
+
+**Migration (Android):** 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*,
diff --git a/auth0_flutter/android/build.gradle b/auth0_flutter/android/build.gradle
index a953c8d94..9ddd46963 100644
--- a/auth0_flutter/android/build.gradle
+++ b/auth0_flutter/android/build.gradle
@@ -85,7 +85,7 @@ plugins.withId("org.jetbrains.kotlin.android") {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
- implementation 'com.auth0.android:auth0:4.1.0'
+ implementation 'com.auth0.android:auth0:4.2.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'
diff --git a/auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasskeyCredentialExchangeApiRequestHandlerTest.kt b/auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasskeyCredentialExchangeApiRequestHandlerTest.kt
index fd6167860..b96f83fc4 100644
--- a/auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasskeyCredentialExchangeApiRequestHandlerTest.kt
+++ b/auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasskeyCredentialExchangeApiRequestHandlerTest.kt
@@ -206,7 +206,7 @@ class PasskeyCredentialExchangeApiRequestHandlerTest {
val request = MethodCallRequest(account = mockAccount, options)
doReturn(mockBuilder).`when`(mockApi)
- .signinWithPasskey(any(), any(), anyOrNull(), anyOrNull())
+ .signinWithPasskey(any(), any(), anyOrNull(), anyOrNull(), any(), any())
doReturn(mockBuilder).`when`(mockBuilder).validateClaims()
doReturn(mockBuilder).`when`(mockBuilder).setScope(any())
doReturn(mockBuilder).`when`(mockBuilder).setAudience(any())
@@ -218,7 +218,9 @@ class PasskeyCredentialExchangeApiRequestHandlerTest {
eq("test-auth-session"),
any(),
eq("test-connection"),
- eq("test-org")
+ eq("test-org"),
+ any(),
+ any()
)
verify(mockBuilder).validateClaims()
verify(mockBuilder).setScope("openid profile")
@@ -241,7 +243,7 @@ class PasskeyCredentialExchangeApiRequestHandlerTest {
val request = MethodCallRequest(account = mockAccount, options)
doReturn(mockBuilder).`when`(mockApi)
- .signinWithPasskey(any(), any(), anyOrNull(), anyOrNull())
+ .signinWithPasskey(any(), any(), anyOrNull(), anyOrNull(), any(), any())
doReturn(mockBuilder).`when`(mockBuilder).validateClaims()
handler.handle(mockApi, request, mockResult)
@@ -250,7 +252,9 @@ class PasskeyCredentialExchangeApiRequestHandlerTest {
eq("test-auth-session"),
any(),
anyOrNull(),
- anyOrNull()
+ anyOrNull(),
+ any(),
+ any()
)
verify(mockBuilder).validateClaims()
verify(mockBuilder).start(any())
@@ -272,7 +276,7 @@ class PasskeyCredentialExchangeApiRequestHandlerTest {
AuthenticationException(code = "test-code", description = "test-description")
doReturn(mockBuilder).`when`(mockApi)
- .signinWithPasskey(any(), any(), anyOrNull(), anyOrNull())
+ .signinWithPasskey(any(), any(), anyOrNull(), anyOrNull(), any(), any())
doReturn(mockBuilder).`when`(mockBuilder).validateClaims()
doAnswer {
val cb = it.getArgument>(0)
@@ -307,7 +311,7 @@ class PasskeyCredentialExchangeApiRequestHandlerTest {
)
doReturn(mockBuilder).`when`(mockApi)
- .signinWithPasskey(any(), any(), anyOrNull(), anyOrNull())
+ .signinWithPasskey(any(), any(), anyOrNull(), anyOrNull(), any(), any())
doReturn(mockBuilder).`when`(mockBuilder).validateClaims()
doAnswer {
val cb = it.getArgument>(0)
diff --git a/auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasskeySignupChallengeApiRequestHandlerTest.kt b/auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasskeySignupChallengeApiRequestHandlerTest.kt
index 7770165e4..7608ead36 100644
--- a/auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasskeySignupChallengeApiRequestHandlerTest.kt
+++ b/auth0_flutter/android/src/test/kotlin/com/auth0/auth0_flutter/request_handlers/api/PasskeySignupChallengeApiRequestHandlerTest.kt
@@ -61,7 +61,7 @@ class PasskeySignupChallengeApiRequestHandlerTest {
val mockResult = mock()
val request = MethodCallRequest(account = mockAccount, options)
- doReturn(mockBuilder).`when`(mockApi).signupWithPasskey(any(), anyOrNull(), anyOrNull())
+ doReturn(mockBuilder).`when`(mockApi).signupWithPasskey(any(), anyOrNull(), anyOrNull(), anyOrNull())
handler.handle(mockApi, request, mockResult)
@@ -77,7 +77,8 @@ class PasskeySignupChallengeApiRequestHandlerTest {
this.picture == "https://www.okta.com"
},
eq("test-connection"),
- eq("test-org")
+ eq("test-org"),
+ isNull()
)
verify(mockBuilder).start(any())
}
@@ -92,7 +93,7 @@ class PasskeySignupChallengeApiRequestHandlerTest {
val mockResult = mock()
val request = MethodCallRequest(account = mockAccount, options)
- doReturn(mockBuilder).`when`(mockApi).signupWithPasskey(any(), anyOrNull(), anyOrNull())
+ doReturn(mockBuilder).`when`(mockApi).signupWithPasskey(any(), anyOrNull(), anyOrNull(), anyOrNull())
doAnswer {
val cb = it.getArgument>(0)
cb.onSuccess(challenge())
@@ -121,7 +122,7 @@ class PasskeySignupChallengeApiRequestHandlerTest {
val exception =
AuthenticationException(code = "test-code", description = "test-description")
- doReturn(mockBuilder).`when`(mockApi).signupWithPasskey(any(), anyOrNull(), anyOrNull())
+ doReturn(mockBuilder).`when`(mockApi).signupWithPasskey(any(), anyOrNull(), anyOrNull(), anyOrNull())
doAnswer {
val cb = it.getArgument>(0)
cb.onFailure(exception)
diff --git a/auth0_flutter/darwin/auth0_flutter.podspec b/auth0_flutter/darwin/auth0_flutter.podspec
index befd3a9e8..5dfd104c7 100644
--- a/auth0_flutter/darwin/auth0_flutter.podspec
+++ b/auth0_flutter/darwin/auth0_flutter.podspec
@@ -13,13 +13,13 @@ Pod::Spec.new do |s|
s.source = { :path => '.' }
s.source_files = 'auth0_flutter/Sources/auth0_flutter/**/*.swift'
- s.ios.deployment_target = '14.0'
+ s.ios.deployment_target = '15.0'
s.ios.dependency 'Flutter'
- s.osx.deployment_target = '11.0'
+ s.osx.deployment_target = '12.0'
s.osx.dependency 'FlutterMacOS'
- s.dependency 'Auth0', '3.0.2'
+ s.dependency 'Auth0', '3.2.0'
s.dependency 'JWTDecode', '4.0.0'
s.dependency 'SimpleKeychain', '1.3.0'
diff --git a/auth0_flutter/darwin/auth0_flutter/Package.swift b/auth0_flutter/darwin/auth0_flutter/Package.swift
index ca61ab968..9dfc01048 100644
--- a/auth0_flutter/darwin/auth0_flutter/Package.swift
+++ b/auth0_flutter/darwin/auth0_flutter/Package.swift
@@ -4,14 +4,14 @@ import PackageDescription
let package = Package(
name: "auth0_flutter",
platforms: [
- .iOS("14.0"),
- .macOS("11.0"),
+ .iOS("15.0"),
+ .macOS("12.0"),
],
products: [
.library(name: "auth0-flutter", targets: ["auth0_flutter"]),
],
dependencies: [
- .package(url: "https://github.com/auth0/Auth0.swift", exact: "3.0.2"),
+ .package(url: "https://github.com/auth0/Auth0.swift", exact: "3.2.0"),
.package(url: "https://github.com/auth0/JWTDecode.swift", exact: "4.0.0"),
.package(url: "https://github.com/auth0/SimpleKeychain", exact: "1.3.0"),
],
diff --git a/auth0_flutter/example/ios/Tests/AuthAPI/AuthAPISpies.swift b/auth0_flutter/example/ios/Tests/AuthAPI/AuthAPISpies.swift
index 0db8d4772..39decee88 100644
--- a/auth0_flutter/example/ios/Tests/AuthAPI/AuthAPISpies.swift
+++ b/auth0_flutter/example/ios/Tests/AuthAPI/AuthAPISpies.swift
@@ -84,9 +84,10 @@ class SpyAuthentication: Authentication, @unchecked Sendable {
return request(databaseUserResult)
}
- func resetPassword(email: String, connection: String) -> any Requestable {
+ func resetPassword(email: String, connection: String, organization: String?) -> any Requestable {
arguments["email"] = email
arguments["connection"] = connection
+ arguments["organization"] = organization
calledResetPassword = true
return request(voidResult)
}
@@ -144,20 +145,12 @@ class SpyAuthentication: Authentication, @unchecked Sendable {
return request(.success(JWKS(keys: [])))
}
- #if PASSKEYS_PLATFORM
+ // The next two are kept outside `#if PASSKEYS_PLATFORM`: the macOS test target
+ // does not define that flag, and since Auth0.swift 3.2.0 no protocol extension
+ // method matches these requirements' signatures, so the spy must always provide them.
@available(iOS 16.6, macOS 13.5, visionOS 1.0, *)
- func passkeyLoginChallenge(connection: String?,
- organization: String?) -> any Requestable {
- arguments["connection"] = connection
- arguments["organization"] = organization
- calledPasskeyLoginChallenge = true
- return request(passkeyLoginChallengeResultOverride
- ?? SpyAuthentication.passkeyLoginChallengeResult)
- }
-
- @available(iOS 16.6, macOS 13.5, visionOS 1.0, *)
- func login(passkey: LoginPasskey,
- challenge: PasskeyLoginChallenge,
+ func login(passkey: SignupPasskey,
+ challenge: PasskeySignupChallenge,
connection: String?,
audience: String?,
scope: String,
@@ -166,7 +159,7 @@ class SpyAuthentication: Authentication, @unchecked Sendable {
arguments["audience"] = audience
arguments["scope"] = scope
arguments["organization"] = organization
- calledLoginWithPasskey = true
+ calledSignupWithPasskey = true
return tokenRequest(credentialsResult)
}
@@ -194,13 +187,28 @@ class SpyAuthentication: Authentication, @unchecked Sendable {
arguments["organization"] = organization
arguments["userMetadata"] = userMetadata
calledPasskeySignupChallenge = true
+ #if PASSKEYS_PLATFORM
return request(passkeySignupChallengeResultOverride
?? SpyAuthentication.passkeySignupChallengeResult)
+ #else
+ return request(.failure(AuthenticationError(info: [:], statusCode: 0)))
+ #endif
}
+ #if PASSKEYS_PLATFORM
@available(iOS 16.6, macOS 13.5, visionOS 1.0, *)
- func login(passkey: SignupPasskey,
- challenge: PasskeySignupChallenge,
+ func passkeyLoginChallenge(connection: String?,
+ organization: String?) -> any Requestable {
+ arguments["connection"] = connection
+ arguments["organization"] = organization
+ calledPasskeyLoginChallenge = true
+ return request(passkeyLoginChallengeResultOverride
+ ?? SpyAuthentication.passkeyLoginChallengeResult)
+ }
+
+ @available(iOS 16.6, macOS 13.5, visionOS 1.0, *)
+ func login(passkey: LoginPasskey,
+ challenge: PasskeyLoginChallenge,
connection: String?,
audience: String?,
scope: String,
@@ -209,6 +217,49 @@ class SpyAuthentication: Authentication, @unchecked Sendable {
arguments["audience"] = audience
arguments["scope"] = scope
arguments["organization"] = organization
+ calledLoginWithPasskey = true
+ return tokenRequest(credentialsResult)
+ }
+
+ @available(iOS 16.6, macOS 13.5, visionOS 1.0, *)
+ func passkeySignupChallenge(email: String?,
+ phoneNumber: String?,
+ username: String?,
+ name: String?,
+ givenName: String?,
+ familyName: String?,
+ nickname: String?,
+ picture: String?,
+ userMetadata: [String: String]?,
+ connection: String?,
+ organization: String?,
+ deliveryMethod: DeliveryMethod?) -> any Requestable {
+ return passkeySignupChallenge(email: email,
+ phoneNumber: phoneNumber,
+ username: username,
+ name: name,
+ givenName: givenName,
+ familyName: familyName,
+ nickname: nickname,
+ picture: picture,
+ userMetadata: userMetadata,
+ connection: connection,
+ organization: organization)
+ }
+
+ @available(iOS 16.6, macOS 13.5, visionOS 1.0, *)
+ func login(passkey: SignupPasskey,
+ verificationRequired: [PasskeyVerificationMethod],
+ authSession: String,
+ connection: String?,
+ audience: String?,
+ scope: String,
+ organization: String?,
+ verification: [PasskeyVerificationMethod: String]) -> any TokenRequestable {
+ arguments["connection"] = connection
+ arguments["audience"] = audience
+ arguments["scope"] = scope
+ arguments["organization"] = organization
calledSignupWithPasskey = true
return tokenRequest(credentialsResult)
}
diff --git a/auth0_flutter/example/web/index.html b/auth0_flutter/example/web/index.html
index a7d5d25d2..782133b68 100644
--- a/auth0_flutter/example/web/index.html
+++ b/auth0_flutter/example/web/index.html
@@ -47,7 +47,7 @@
// Create promise that resolves when Auth0 SDK is loaded
window.auth0SdkReady = new Promise((resolve, reject) => {
const script = document.createElement('script');
- script.src = 'https://unpkg.com/@auth0/auth0-spa-js@2.24.0/dist/auth0-spa-js.production.js';
+ script.src = 'https://unpkg.com/@auth0/auth0-spa-js@2.29.0/dist/auth0-spa-js.production.js';
script.onload = () => {
if (window.auth0 && window.auth0.Auth0Client) {
diff --git a/auth0_flutter/ios/auth0_flutter.podspec b/auth0_flutter/ios/auth0_flutter.podspec
index 6c1b397b7..2f5dfc09a 100644
--- a/auth0_flutter/ios/auth0_flutter.podspec
+++ b/auth0_flutter/ios/auth0_flutter.podspec
@@ -13,13 +13,13 @@ Pod::Spec.new do |s|
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
- s.ios.deployment_target = '14.0'
+ s.ios.deployment_target = '15.0'
s.ios.dependency 'Flutter'
- s.osx.deployment_target = '11.0'
+ s.osx.deployment_target = '12.0'
s.osx.dependency 'FlutterMacOS'
- s.dependency 'Auth0', '3.0.2'
+ s.dependency 'Auth0', '3.2.0'
s.dependency 'JWTDecode', '4.0.0'
s.dependency 'SimpleKeychain', '1.3.0'
diff --git a/auth0_flutter/macos/auth0_flutter.podspec b/auth0_flutter/macos/auth0_flutter.podspec
index 6c1b397b7..2f5dfc09a 100644
--- a/auth0_flutter/macos/auth0_flutter.podspec
+++ b/auth0_flutter/macos/auth0_flutter.podspec
@@ -13,13 +13,13 @@ Pod::Spec.new do |s|
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
- s.ios.deployment_target = '14.0'
+ s.ios.deployment_target = '15.0'
s.ios.dependency 'Flutter'
- s.osx.deployment_target = '11.0'
+ s.osx.deployment_target = '12.0'
s.osx.dependency 'FlutterMacOS'
- s.dependency 'Auth0', '3.0.2'
+ s.dependency 'Auth0', '3.2.0'
s.dependency 'JWTDecode', '4.0.0'
s.dependency 'SimpleKeychain', '1.3.0'