diff --git a/EXAMPLES.md b/EXAMPLES.md index a7936901..f4fe292e 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -738,10 +738,10 @@ authentication > [!NOTE] > This feature is currently available in [Early Access](https://auth0.com/docs/troubleshoot/product-lifecycle/product-release-stages#early-access). Please reach out to Auth0 support to get it enabled for your tenant. -[DPoP](https://www.rfc-editor.org/rfc/rfc9449.html) (Demonstrating Proof of Posession) is an application-level mechanism for sender-constraining OAuth 2.0 access and refresh tokens by proving that the app is in possession of a certain private key. You can enable it by calling the `useDPoP()` method. This ensures that DPoP proofs are generated for requests made through the AuthenticationAPI client. +[DPoP](https://www.rfc-editor.org/rfc/rfc9449.html) (Demonstrating Proof of Possession) is an application-level mechanism for sender-constraining OAuth 2.0 access and refresh tokens by proving that the app is in possession of a certain private key. You can enable it by calling the `useDPoP(context: Context)` method. This ensures that DPoP proofs are generated for requests made through the AuthenticationAPI client. ```kotlin -val client = AuthenticationAPIClient(account).useDPoP() +val client = AuthenticationAPIClient(account).useDPoP(context) ``` [!IMPORTANT] @@ -785,6 +785,17 @@ DPoP.clearKeyPair() ``` +To use DPoP with `SecureCredentialsManager` you need to pass an instance of the `AuthenticationAPIClient` with DPoP enabled to the `SecureCredentialsManager` constructor. + +```kotlin +val auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN") +val apiClient = AuthenticationAPIClient(auth0).useDPoP(this) +val storage = SharedPreferencesStorage(this) +val manager = SecureCredentialsManager(apiClient, this, auth0, storage) + +``` + + > [!NOTE] > DPoP is supported only on Android version 6.0 (API level 23) and above. Trying to use DPoP in any older versions will result in an exception. @@ -1382,6 +1393,28 @@ SecureCredentialsManager manager = new SecureCredentialsManager(this, account, s ``` +#### Using a Custom AuthenticationAPIClient + +If you need to configure the `AuthenticationAPIClient` with advanced features (such as DPoP), you can pass your own configured instance to `SecureCredentialsManager`: + +```kotlin +val auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN") +val apiClient = AuthenticationAPIClient(auth0).useDPoP(this) +val storage = SharedPreferencesStorage(this) +val manager = SecureCredentialsManager(apiClient, this, auth0, storage) +``` + +
+ Using Java + +```java +Auth0 auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN"); +AuthenticationAPIClient apiClient = new AuthenticationAPIClient(auth0).useDPoP(this); +Storage storage = new SharedPreferencesStorage(this); +SecureCredentialsManager manager = new SecureCredentialsManager(apiClient, this, auth0, storage); +``` +
+ #### Requiring Authentication You can require the user authentication to obtain credentials. This will make the manager prompt the user with the device's configured Lock Screen, which they must pass correctly in order to obtain the credentials. **This feature is only available on devices where the user has setup a secured Lock Screen** (PIN, Pattern, Password or Fingerprint). @@ -1419,6 +1452,49 @@ SecureCredentialsManager secureCredentialsManager = new SecureCredentialsManager ``` +You can also combine biometric authentication with a custom `AuthenticationAPIClient`: + +```kotlin +val auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN") +val apiClient = AuthenticationAPIClient(auth0).useDPoP(this) +val localAuthenticationOptions = + LocalAuthenticationOptions.Builder() + .setTitle("Authenticate") + .setDescription("Accessing Credentials") + .setAuthenticationLevel(AuthenticationLevel.STRONG) + .setNegativeButtonText("Cancel") + .setDeviceCredentialFallback(true) + .setPolicy(BiometricPolicy.Session(300)) + .build() +val storage = SharedPreferencesStorage(this) +val manager = SecureCredentialsManager( + apiClient, this, auth0, storage, fragmentActivity, + localAuthenticationOptions +) +``` + +
+ Using Java + +```java +Auth0 auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN"); +AuthenticationAPIClient apiClient = new AuthenticationAPIClient(auth0).useDPoP(this); +LocalAuthenticationOptions localAuthenticationOptions = + new LocalAuthenticationOptions.Builder() + .setTitle("Authenticate") + .setDescription("Accessing Credentials") + .setAuthenticationLevel(AuthenticationLevel.STRONG) + .setNegativeButtonText("Cancel") + .setDeviceCredentialFallback(true) + .setPolicy(new BiometricPolicy.Session(300)) + .build(); +Storage storage = new SharedPreferencesStorage(this); +SecureCredentialsManager secureCredentialsManager = new SecureCredentialsManager( + apiClient, this, auth0, storage, fragmentActivity, + localAuthenticationOptions); +``` +
+ **Points to be Noted**: On Android API 29 and below, specifying **DEVICE_CREDENTIAL** alone as the authentication level is not supported. diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt index b60a6270..ef5c3806 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt @@ -1,28 +1,24 @@ package com.auth0.android.authentication.storage import android.text.TextUtils -import android.util.Base64 import android.util.Log import androidx.annotation.VisibleForTesting import com.auth0.android.authentication.AuthenticationAPIClient import com.auth0.android.authentication.AuthenticationException -import com.auth0.android.authentication.storage.SecureCredentialsManager.Companion.KEY_CREDENTIALS import com.auth0.android.callback.Callback import com.auth0.android.request.internal.GsonProvider import com.auth0.android.request.internal.Jwt import com.auth0.android.result.APICredentials import com.auth0.android.result.Credentials -import com.auth0.android.result.OptionalCredentials import com.auth0.android.result.SSOCredentials import com.auth0.android.result.UserProfile import com.auth0.android.result.toAPICredentials import com.google.gson.Gson import kotlinx.coroutines.suspendCancellableCoroutine -import java.util.* +import java.util.Date +import java.util.Locale import java.util.concurrent.Executor import java.util.concurrent.Executors -import kotlin.collections.component1 -import kotlin.collections.component2 import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt index 22c426ec..87644f5f 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt @@ -11,7 +11,6 @@ import com.auth0.android.authentication.AuthenticationAPIClient import com.auth0.android.authentication.AuthenticationException import com.auth0.android.callback.Callback import com.auth0.android.request.internal.GsonProvider -import com.auth0.android.request.internal.Jwt import com.auth0.android.result.APICredentials import com.auth0.android.result.Credentials import com.auth0.android.result.OptionalCredentials @@ -19,16 +18,12 @@ import com.auth0.android.result.SSOCredentials import com.auth0.android.result.UserProfile import com.auth0.android.result.toAPICredentials import com.google.gson.Gson -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import java.lang.ref.WeakReference -import java.util.* +import java.util.Date +import java.util.Locale import java.util.concurrent.Executor import java.util.concurrent.atomic.AtomicLong -import kotlin.collections.component1 -import kotlin.collections.component2 import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -65,6 +60,34 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT storage: Storage, ) : this( AuthenticationAPIClient(auth0), + context, + auth0, + storage + ) + + /** + * Creates a new SecureCredentialsManager to handle Credentials with a custom AuthenticationAPIClient instance. + * Use this constructor when you need to configure the API client with advanced features like DPoP. + * + * Example usage: + * ``` + * val auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN") + * val apiClient = AuthenticationAPIClient(auth0).useDPoP(context) + * val manager = SecureCredentialsManager(apiClient, context, auth0, storage) + * ``` + * + * @param apiClient a configured AuthenticationAPIClient instance + * @param context a valid context + * @param auth0 the Auth0 account information to use + * @param storage the storage implementation to use + */ + public constructor( + apiClient: AuthenticationAPIClient, + context: Context, + auth0: Auth0, + storage: Storage + ) : this( + apiClient, storage, CryptoUtil(context, storage, KEY_ALIAS), JWTDecoder(), @@ -89,6 +112,50 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT localAuthenticationOptions: LocalAuthenticationOptions ) : this( AuthenticationAPIClient(auth0), + context, + auth0, + storage, + fragmentActivity, + localAuthenticationOptions + ) + + + /** + * Creates a new SecureCredentialsManager to handle Credentials with biometrics Authentication + * and a custom AuthenticationAPIClient instance. + * Use this constructor when you need to configure the API client with advanced features like DPoP + * along with biometric authentication. + * + * Example usage: + * ``` + * val auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN") + * val apiClient = AuthenticationAPIClient(auth0).useDPoP(context) + * val manager = SecureCredentialsManager( + * apiClient, + * context, + * auth0, + * storage, + * fragmentActivity, + * localAuthenticationOptions + * ) + * ``` + * + * @param apiClient a configured AuthenticationAPIClient instance + * @param context a valid context + * @param auth0 the Auth0 account information to use + * @param storage the storage implementation to use + * @param fragmentActivity the FragmentActivity to use for the biometric authentication + * @param localAuthenticationOptions the options of type [LocalAuthenticationOptions] to use for the biometric authentication + */ + public constructor( + apiClient: AuthenticationAPIClient, + context: Context, + auth0: Auth0, + storage: Storage, + fragmentActivity: FragmentActivity, + localAuthenticationOptions: LocalAuthenticationOptions + ) : this( + apiClient, storage, CryptoUtil(context, storage, KEY_ALIAS), JWTDecoder(), @@ -270,7 +337,7 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT if (credentials == null) { return null } - return credentials.user + return credentials.user } /** @@ -1138,7 +1205,7 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT internal fun isBiometricSessionValid(): Boolean { val lastAuth = lastBiometricAuthTime.get() if (lastAuth == NO_SESSION) return false // No session exists - + return when (val policy = biometricPolicy) { is BiometricPolicy.Session, is BiometricPolicy.AppLifecycle -> { @@ -1149,6 +1216,7 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT } * 1000L System.currentTimeMillis() - lastAuth < timeoutMillis } + is BiometricPolicy.Always -> false } }