Skip to content
Open
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
15 changes: 12 additions & 3 deletions auth0_flutter/EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,13 +443,16 @@ final auth0 = Auth0('YOUR_AUTH0_DOMAIN', 'YOUR_AUTH0_CLIENT_ID',

You can enable an additional level of user authentication before retrieving credentials using the local authentication supported by the device, for example PIN or fingerprint on Android, and Face ID or Touch ID on iOS.

To enable this, pass a `LocalAuthentication` instance when you create your `Auth0` object.

```dart
const localAuthentication =
LocalAuthentication(title: 'Please authenticate to continue');
final auth0 = Auth0('YOUR_AUTH0_DOMAIN', 'YOUR_AUTH0_CLIENT_ID',
localAuthentication: localAuthentication);
final credentials = await auth0.credentialsManager.credentials();
```
> ⚠️ On Android, your app's MainActivity.kt file must extend FlutterFragmentActivity instead of FlutterActivity for biometric prompts to work.

Check the [API documentation](https://pub.dev/documentation/auth0_flutter_platform_interface/latest/auth0_flutter_platform_interface/LocalAuthentication-class.html) to learn more about the available `LocalAuthentication` properties.

Expand Down Expand Up @@ -490,10 +493,16 @@ The Credentials Manager will only throw `CredentialsManagerException` exceptions

```dart
try {
final credentials = await auth0.credentialsManager.credentials();
// ...
final credentials = await auth0.credentialsManager.credentials();
// ...
} on CredentialsManagerException catch (e) {
print(e);
if (e.isNoCredentialsFound) {
print("No credentials stored.");
} else if (e.isTokenRenewFailed) {
print("Failed to renew tokens.");
} else {
print(e);
}
}
```

Expand Down
30 changes: 18 additions & 12 deletions auth0_flutter/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ buildscript {
}

dependencies {
classpath "com.android.tools.build:gradle:8.4.0"
classpath "com.android.tools.build:gradle:8.3.0"
Copy link
Contributor

Choose a reason for hiding this comment

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

Why this is being downgraded ?

classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Expand All @@ -31,18 +31,18 @@ rootProject.allprojects {
}

android {
compileSdk 34
compileSdk 35

if (project.android.hasProperty("namespace")) {
namespace libApplicationId
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
Copy link
Contributor

Choose a reason for hiding this comment

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

Lets updated the java version to 17 .

}

kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '11'
Copy link
Contributor

Choose a reason for hiding this comment

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

Lets update to 17

}

sourceSets {
Expand All @@ -51,9 +51,10 @@ android {
}

defaultConfig {
minSdkVersion 21
minSdkVersion 24
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders = [auth0Domain: "test-domain", auth0Scheme: "test"]
manifestPlaceholders = [auth0Domain: "dev-z0xy0f8x5xj51m2q.us.auth0.com", auth0Scheme: "https"]
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't hardcode your domain id here. Use a place holder in commited changes

consumerProguardFiles '../proguard/proguard-gson.pro', '../proguard/proguard-okio.pro', '../proguard/proguard-jetpack.pro'
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these from the Auth0.Android SDK ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The proguard-jetpack.pro file is a new requirement for v3.x of the native SDK because it now uses the AndroidX Biometric and Credentials libraries, and this file prevents their classes from being stripped.

}

buildTypes {
Expand All @@ -71,15 +72,20 @@ android {
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
//noinspection GradleDynamicVersion
implementation 'com.auth0.android:auth0:2.11.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'com.auth0.android:auth0:3.9.0'
Copy link
Contributor

Choose a reason for hiding this comment

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

Use the latest version of 3.10.0

implementation "androidx.biometric:biometric:1.1.0"
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'androidx.browser:browser:1.4.0'
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.6.0'
implementation 'com.google.androidbrowserhelper:androidbrowserhelper:2.4.0'
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need all these dependencies ? I don't think so

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need them because auth0-android:3.x changed its dependency declarations from api to implementation. This means its own dependencies (like appcompat for UI components and core-ktx for utilities) are no longer transitively exposed to our plugin.


testImplementation 'junit:junit:4.13.2'
testImplementation 'org.hamcrest:java-hamcrest:2.0.0.0'
testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"
testImplementation 'com.jayway.awaitility:awaitility:1.7.0'
testImplementation 'org.robolectric:robolectric:4.6.1'
testImplementation 'org.robolectric:robolectric:4.8.1'
testImplementation 'androidx.test.espresso:espresso-intents:3.5.1'
testImplementation 'com.auth0:java-jwt:3.19.1'
}
}
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.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@ class Auth0FlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
webAuthCallHandler.activity = binding.activity
credentialsManagerCallHandler.activity = binding.activity

binding.addActivityResultListener(credentialsManagerCallHandler)
}

override fun onDetachedFromActivityForConfigChanges() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,70 @@ package com.auth0.auth0_flutter

import android.app.Activity
import android.content.Context
import android.content.Intent
import androidx.annotation.NonNull
import com.auth0.android.authentication.AuthenticationAPIClient
import androidx.fragment.app.FragmentActivity
import com.auth0.android.authentication.storage.AuthenticationLevel
import com.auth0.android.authentication.storage.LocalAuthenticationOptions
import com.auth0.android.authentication.storage.SecureCredentialsManager
import com.auth0.android.authentication.storage.SharedPreferencesStorage
import com.auth0.auth0_flutter.request_handlers.MethodCallRequest
import com.auth0.auth0_flutter.request_handlers.credentials_manager.CredentialsManagerRequestHandler
import com.auth0.auth0_flutter.utils.RequestCodes
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry

class CredentialsManagerMethodCallHandler(private val requestHandlers: List<CredentialsManagerRequestHandler>) :
MethodCallHandler, PluginRegistry.ActivityResultListener {
class CredentialsManagerMethodCallHandler(private val requestHandlers: List<CredentialsManagerRequestHandler>) : MethodCallHandler {
lateinit var activity: Activity
lateinit var context: Context

var credentialsManager: SecureCredentialsManager? = null

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
val requestHandler = requestHandlers.find { it.method == call.method }

if (requestHandler != null) {
val request = MethodCallRequest.fromCall(call)
val activity = this.activity

val configuration =
request.data["credentialsManagerConfiguration"] as Map<*, *>?
val configuration = request.data["credentialsManagerConfiguration"] as Map<*, *>?

val sharedPreferenceConfiguration = configuration?.get("android")
val sharedPreferenceName: String? = if (sharedPreferenceConfiguration != null) {
(sharedPreferenceConfiguration as Map<String, String>).get("sharedPreferencesName")
(sharedPreferenceConfiguration as Map<String, String>)["sharedPreferencesName"]
} else null

val api = AuthenticationAPIClient(request.account)
val storage = sharedPreferenceName?.let {
SharedPreferencesStorage(context, it)
} ?: SharedPreferencesStorage(context)
credentialsManager =
credentialsManager ?: SecureCredentialsManager(context, api, storage)
Copy link
Contributor

Choose a reason for hiding this comment

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

You shouldn't remove this code. This is to ensure we don't create a new instance of CredentialsManager each time this method is called . Your current implementation creates a new instance of SecureCredentialsManager each time. Add this check in your current workflow


val credentialsManager = credentialsManager as SecureCredentialsManager
val localAuthentication =
request.data.get("localAuthentication") as Map<String, String>?
val localAuthentication = request.data["localAuthentication"] as Map<String, String>?
val credentialsManager: SecureCredentialsManager

if (localAuthentication != null) {
val title = localAuthentication["title"]
val description = localAuthentication["description"]
credentialsManager.requireAuthentication(
activity,
RequestCodes.AUTH_REQ_CODE,
title,
description
)
if (activity !is FragmentActivity) {
result.error(
"credentialsManager#biometric-error",
"The Activity is not a FragmentActivity, which is required for biometric authentication.",
null
)
return
}

val builder = LocalAuthenticationOptions.Builder()
localAuthentication["title"]?.let { builder.setTitle(it) }
localAuthentication["description"]?.let { builder.setDescription(it) }
localAuthentication["cancelTitle"]?.let { builder.setNegativeButtonText(it) }

builder.setAuthenticationLevel(AuthenticationLevel.STRONG)
builder.setDeviceCredentialFallback(true)


credentialsManager = SecureCredentialsManager(context, request.account, storage, activity, builder.build())
} else {
credentialsManager = SecureCredentialsManager(context, request.account, storage)
}

requestHandler.handle(credentialsManager, context, request, result)
} else {
result.notImplemented()
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
return credentialsManager?.checkAuthenticationResult(requestCode, resultCode) ?: true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class MethodCallRequest {
)

val accountMap = args["_account"] as Map<String, String>
val account = Auth0(
val account = Auth0.getInstance(
accountMap["clientId"] as String,
accountMap["domain"] as String
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.flutter.plugin.common.MethodChannel
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.*
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.robolectric.RobolectricTestRunner
Expand All @@ -32,7 +33,7 @@ class Auth0FlutterPluginTest {
)
}

assertMethodcallHandler<Auth0FlutterAuthMethodCallHandler>(0)
assertMethodcallHandler<Auth0FlutterWebAuthMethodCallHandler>(0)
assertMethodcallHandler<Auth0FlutterAuthMethodCallHandler>(1)
assertMethodcallHandler<CredentialsManagerMethodCallHandler>(2)

Expand Down Expand Up @@ -89,7 +90,7 @@ class Auth0FlutterPluginTest {
fun <TMethodCallHandler : MethodChannel.MethodCallHandler> getHandler(i: Int): TMethodCallHandler {
val captor = argumentCaptor<MethodChannel.MethodCallHandler>()

verify(constructed[i]).setMethodCallHandler(captor.capture())
verify(constructed[i], atLeastOnce()).setMethodCallHandler(captor.capture())

@Suppress("UNCHECKED_CAST")
return captor.firstValue as TMethodCallHandler
Expand All @@ -104,7 +105,7 @@ class Auth0FlutterPluginTest {
}

@Test
fun `should call binding addActivityResultListener for CredentialsManager on onAttachedToActivity`() {
fun `should NOT call binding addActivityResultListener on onAttachedToActivity`() {
mockConstruction(MethodChannel::class.java).use {
val plugin = Auth0FlutterPlugin()

Expand All @@ -120,7 +121,7 @@ class Auth0FlutterPluginTest {

plugin.onAttachedToActivity(mockActivityBindings)

verify(mockActivityBindings).addActivityResultListener(
verify(mockActivityBindings, never()).addActivityResultListener(
any<CredentialsManagerMethodCallHandler>()
)
}
Expand Down
Loading
Loading