Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/actions/setup-darwin/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

---
Expand Down
4 changes: 2 additions & 2 deletions auth0_flutter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |

Expand Down Expand Up @@ -370,7 +370,7 @@ final auth0Web = Auth0Web('YOUR_AUTH0_DOMAIN', 'YOUR_AUTH0_CLIENT_ID');
Finally, in your `index.html` add the following `<script>` tag:

```html
<script src="https://cdn.auth0.com/js/auth0-spa-js/2.24/auth0-spa-js.production.js" defer></script>
<script src="https://cdn.auth0.com/js/auth0-spa-js/2.29/auth0-spa-js.production.js" defer></script>
```

> ⚠️ 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`.
Expand Down
12 changes: 10 additions & 2 deletions auth0_flutter/V3_MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,26 @@ 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** |
| 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
**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*,
Expand Down
2 changes: 1 addition & 1 deletion auth0_flutter/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ class PasskeyCredentialExchangeApiRequestHandlerTest {
val request = MethodCallRequest(account = mockAccount, options)

doReturn(mockBuilder).`when`(mockApi)
.signinWithPasskey(any<String>(), any<String>(), anyOrNull(), anyOrNull())
.signinWithPasskey(any<String>(), any<String>(), anyOrNull(), anyOrNull(), any(), any())
doReturn(mockBuilder).`when`(mockBuilder).validateClaims()
doReturn(mockBuilder).`when`(mockBuilder).setScope(any())
doReturn(mockBuilder).`when`(mockBuilder).setAudience(any())
Expand All @@ -218,7 +218,9 @@ class PasskeyCredentialExchangeApiRequestHandlerTest {
eq("test-auth-session"),
any<String>(),
eq("test-connection"),
eq("test-org")
eq("test-org"),
any(),
any()
)
verify(mockBuilder).validateClaims()
verify(mockBuilder).setScope("openid profile")
Expand All @@ -241,7 +243,7 @@ class PasskeyCredentialExchangeApiRequestHandlerTest {
val request = MethodCallRequest(account = mockAccount, options)

doReturn(mockBuilder).`when`(mockApi)
.signinWithPasskey(any<String>(), any<String>(), anyOrNull(), anyOrNull())
.signinWithPasskey(any<String>(), any<String>(), anyOrNull(), anyOrNull(), any(), any())
doReturn(mockBuilder).`when`(mockBuilder).validateClaims()

handler.handle(mockApi, request, mockResult)
Expand All @@ -250,7 +252,9 @@ class PasskeyCredentialExchangeApiRequestHandlerTest {
eq("test-auth-session"),
any<String>(),
anyOrNull(),
anyOrNull()
anyOrNull(),
any(),
any()
)
verify(mockBuilder).validateClaims()
verify(mockBuilder).start(any())
Expand All @@ -272,7 +276,7 @@ class PasskeyCredentialExchangeApiRequestHandlerTest {
AuthenticationException(code = "test-code", description = "test-description")

doReturn(mockBuilder).`when`(mockApi)
.signinWithPasskey(any<String>(), any<String>(), anyOrNull(), anyOrNull())
.signinWithPasskey(any<String>(), any<String>(), anyOrNull(), anyOrNull(), any(), any())
doReturn(mockBuilder).`when`(mockBuilder).validateClaims()
doAnswer {
val cb = it.getArgument<Callback<Credentials, AuthenticationException>>(0)
Expand Down Expand Up @@ -307,7 +311,7 @@ class PasskeyCredentialExchangeApiRequestHandlerTest {
)

doReturn(mockBuilder).`when`(mockApi)
.signinWithPasskey(any<String>(), any<String>(), anyOrNull(), anyOrNull())
.signinWithPasskey(any<String>(), any<String>(), anyOrNull(), anyOrNull(), any(), any())
doReturn(mockBuilder).`when`(mockBuilder).validateClaims()
doAnswer {
val cb = it.getArgument<Callback<Credentials, AuthenticationException>>(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class PasskeySignupChallengeApiRequestHandlerTest {
val mockResult = mock<Result>()
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)

Expand All @@ -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())
}
Expand All @@ -92,7 +93,7 @@ class PasskeySignupChallengeApiRequestHandlerTest {
val mockResult = mock<Result>()
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<Callback<PasskeyRegistrationChallenge, AuthenticationException>>(0)
cb.onSuccess(challenge())
Expand Down Expand Up @@ -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<Callback<PasskeyRegistrationChallenge, AuthenticationException>>(0)
cb.onFailure(exception)
Expand Down
6 changes: 3 additions & 3 deletions auth0_flutter/darwin/auth0_flutter.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
6 changes: 3 additions & 3 deletions auth0_flutter/darwin/auth0_flutter/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"),

@NandanPrabhu NandanPrabhu Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@utkrishtsahu 3.2.0 has organization support to reset password in swift. lets add in flutter if not already added

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NandanPrabhu will take it after making this GA

.package(url: "https://github.com/auth0/JWTDecode.swift", exact: "4.0.0"),
.package(url: "https://github.com/auth0/SimpleKeychain", exact: "1.3.0"),
],
Expand Down
85 changes: 68 additions & 17 deletions auth0_flutter/example/ios/Tests/AuthAPI/AuthAPISpies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,10 @@ class SpyAuthentication: Authentication, @unchecked Sendable {
return request(databaseUserResult)
}

func resetPassword(email: String, connection: String) -> any Requestable<Void, AuthenticationError> {
func resetPassword(email: String, connection: String, organization: String?) -> any Requestable<Void, AuthenticationError> {
arguments["email"] = email
arguments["connection"] = connection
arguments["organization"] = organization
calledResetPassword = true
return request(voidResult)
}
Expand Down Expand Up @@ -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<PasskeyLoginChallenge, AuthenticationError> {
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,
Expand All @@ -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)
}

Expand Down Expand Up @@ -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<PasskeyLoginChallenge, AuthenticationError> {
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,
Expand All @@ -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<PasskeySignupChallenge, AuthenticationError> {
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<Credentials, AuthenticationError> {
arguments["connection"] = connection
arguments["audience"] = audience
arguments["scope"] = scope
arguments["organization"] = organization
calledSignupWithPasskey = true
return tokenRequest(credentialsResult)
}
Expand Down
2 changes: 1 addition & 1 deletion auth0_flutter/example/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions auth0_flutter/ios/auth0_flutter.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
6 changes: 3 additions & 3 deletions auth0_flutter/macos/auth0_flutter.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
Loading