From e2c65d4092eda6d580f05d688c9ed6921697ecec Mon Sep 17 00:00:00 2001 From: Prince Mathew Date: Mon, 23 Mar 2026 14:27:01 +0530 Subject: [PATCH 1/6] Added two new exceptions DPOP_KEY_MISSING and DPOP_NOT_CONFIGURED to CredentialsManager class --- .../authentication/AuthenticationAPIClient.kt | 7 ++++++ .../storage/BaseCredentialsManager.kt | 4 ++++ .../storage/CredentialsManagerException.kt | 9 ++++++++ .../main/java/com/auth0/android/dpop/DPoP.kt | 23 +++++++++++++++++++ 4 files changed, 43 insertions(+) diff --git a/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt b/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt index 572ecc51..f33aee9c 100755 --- a/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt @@ -55,6 +55,13 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe private var dPoP: DPoP? = null + /** + * Returns whether DPoP (Demonstrating Proof of Possession) is enabled on this client. + * DPoP is enabled by calling [useDPoP]. + */ + public val isDPoPEnabled: Boolean + get() = dPoP != null + /** * Creates a new API client instance providing Auth0 account info. * diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt index d3ac32d5..c7237586 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt @@ -20,6 +20,10 @@ public abstract class BaseCredentialsManager internal constructor( protected val storage: Storage, private val jwtDecoder: JWTDecoder ) { + + internal companion object { + internal const val KEY_DPOP_THUMBPRINT = "com.auth0.dpop_key_thumbprint" + } private var _clock: Clock = ClockImpl() /** diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt index 9796dbe6..eb52a454 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt @@ -48,6 +48,8 @@ public class CredentialsManagerException : API_ERROR, SSO_EXCHANGE_FAILED, MFA_REQUIRED, + DPOP_KEY_MISSING, + DPOP_NOT_CONFIGURED, UNKNOWN_ERROR } @@ -159,6 +161,11 @@ public class CredentialsManagerException : public val MFA_REQUIRED: CredentialsManagerException = CredentialsManagerException(Code.MFA_REQUIRED) + public val DPOP_KEY_MISSING: CredentialsManagerException = + CredentialsManagerException(Code.DPOP_KEY_MISSING) + public val DPOP_NOT_CONFIGURED: CredentialsManagerException = + CredentialsManagerException(Code.DPOP_NOT_CONFIGURED) + public val UNKNOWN_ERROR: CredentialsManagerException = CredentialsManagerException(Code.UNKNOWN_ERROR) @@ -207,6 +214,8 @@ public class CredentialsManagerException : Code.API_ERROR -> "An error occurred while processing the request." Code.SSO_EXCHANGE_FAILED ->"The exchange of the refresh token for SSO credentials failed." Code.MFA_REQUIRED -> "Multi-factor authentication is required to complete the credential renewal." + Code.DPOP_KEY_MISSING -> "The stored credentials are DPoP-bound but the DPoP key pair is no longer available in the Android KeyStore. This can happen after a device backup/restore, factory reset, or biometric enrollment change. Re-authentication is required." + Code.DPOP_NOT_CONFIGURED -> "The stored credentials are DPoP-bound but the AuthenticationAPIClient used by this CredentialsManager was not configured with useDPoP(context). Call AuthenticationAPIClient(auth0).useDPoP(context) and pass the configured client to CredentialsManager." Code.UNKNOWN_ERROR -> "An unknown error has occurred while fetching the token. Please check the error cause for more details." } } diff --git a/auth0/src/main/java/com/auth0/android/dpop/DPoP.kt b/auth0/src/main/java/com/auth0/android/dpop/DPoP.kt index d84f7f61..b0490575 100644 --- a/auth0/src/main/java/com/auth0/android/dpop/DPoP.kt +++ b/auth0/src/main/java/com/auth0/android/dpop/DPoP.kt @@ -198,6 +198,29 @@ public class DPoP(context: Context) { return HeaderData(token, proof) } + /** + * Returns whether a DPoP key pair currently exists in the Android KeyStore. + * + * This can be used to check if DPoP credentials are still available after events + * like device backup/restore or factory reset, which do not preserve KeyStore entries. + * + * ```kotlin + * + * if (!DPoP.hasKeyPair()) { + * // Key was lost — clear stored credentials and re-authenticate + * } + * + * ``` + * + * @return true if a DPoP key pair exists in the KeyStore, false otherwise. + * @throws DPoPException if there is an error accessing the KeyStore. + */ + @Throws(DPoPException::class) + @JvmStatic + public fun hasKeyPair(): Boolean { + return DPoPUtil.hasKeyPair() + } + /** * Method to clear the DPoP key pair from the keystore. It must be called when the user logs out * to prevent reuse of the key pair in subsequent sessions. From 7145784497532e2d50ed7a5cb5baaedee4728057 Mon Sep 17 00:00:00 2001 From: Prince Mathew Date: Mon, 23 Mar 2026 16:45:56 +0530 Subject: [PATCH 2/6] Saving the Dpop thumbprint --- .../storage/BaseCredentialsManager.kt | 25 +++++++++++++++++++ .../storage/CredentialsManager.kt | 2 ++ .../storage/SecureCredentialsManager.kt | 7 ++++++ 3 files changed, 34 insertions(+) diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt index c7237586..e6be3340 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt @@ -4,6 +4,8 @@ import android.util.Log import androidx.annotation.VisibleForTesting import com.auth0.android.authentication.AuthenticationAPIClient import com.auth0.android.callback.Callback +import com.auth0.android.dpop.DPoPException +import com.auth0.android.dpop.DPoPUtil import com.auth0.android.result.APICredentials import com.auth0.android.result.Credentials import com.auth0.android.result.SSOCredentials @@ -159,6 +161,29 @@ public abstract class BaseCredentialsManager internal constructor( internal val currentTimeInMillis: Long get() = _clock.getCurrentTimeMillis() + /** + * Stores the DPoP key thumbprint if DPoP was used for this credential set. + * Uses a dual strategy to store the thumbprint: + * - credentials.type == "DPoP" when server confirms DPoP but client lacks useDPoP() + * - isDPoPEnabled catches the case where client used DPoP, server returned token_type: "Bearer" + */ + protected fun saveDPoPThumbprint(credentials: Credentials) { + val dpopUsed = credentials.type.equals("DPoP", ignoreCase = true) + || authenticationClient.isDPoPEnabled + if (dpopUsed && DPoPUtil.hasKeyPair()) { + try { + val thumbprint = DPoPUtil.getPublicKeyJWK() + if (thumbprint != null) { + storage.store(KEY_DPOP_THUMBPRINT, thumbprint) + } + } catch (e: DPoPException) { + Log.w(this::class.java.simpleName, "Failed to store DPoP key thumbprint", e) + } + } else { + storage.remove(KEY_DPOP_THUMBPRINT) + } + } + /** * Checks if the stored scope is the same as the requested one. * 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 0cc5c61f..a658f9b8 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 @@ -75,6 +75,7 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting storage.store(KEY_EXPIRES_AT, credentials.expiresAt.time) storage.store(KEY_SCOPE, credentials.scope) storage.store(LEGACY_KEY_CACHE_EXPIRES_AT, credentials.expiresAt.time) + saveDPoPThumbprint(credentials) } /** @@ -714,6 +715,7 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting storage.remove(KEY_EXPIRES_AT) storage.remove(KEY_SCOPE) storage.remove(LEGACY_KEY_CACHE_EXPIRES_AT) + storage.remove(KEY_DPOP_THUMBPRINT) } /** 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 4a367e77..41c05447 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 @@ -189,6 +189,8 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT ) storage.store(LEGACY_KEY_CACHE_EXPIRES_AT, credentials.expiresAt.time) storage.store(KEY_CAN_REFRESH, canRefresh) + storage.store(KEY_TOKEN_TYPE, credentials.type) + saveDPoPThumbprint(credentials) } catch (e: IncompatibleDeviceException) { throw CredentialsManagerException( CredentialsManagerException.Code.INCOMPATIBLE_DEVICE, e @@ -735,6 +737,8 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT storage.remove(KEY_EXPIRES_AT) storage.remove(LEGACY_KEY_CACHE_EXPIRES_AT) storage.remove(KEY_CAN_REFRESH) + storage.remove(KEY_TOKEN_TYPE) + storage.remove(KEY_DPOP_THUMBPRINT) clearBiometricSession() Log.d(TAG, "Credentials were just removed from the storage") } @@ -1251,6 +1255,9 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal const val KEY_ALIAS = "com.auth0.key" + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal const val KEY_TOKEN_TYPE = "com.auth0.token_type" + // Using NO_SESSION to represent "no session" (uninitialized state) private const val NO_SESSION = -1L } From ec2c6ed58ff26d6e72d73fc4190631c1b3c00aaa Mon Sep 17 00:00:00 2001 From: Prince Mathew <17837162+pmathew92@users.noreply.github.com> Date: Tue, 24 Mar 2026 14:37:11 +0530 Subject: [PATCH 3/6] Added two new exceptions to CredentialsManager class (#939) --- .../authentication/AuthenticationAPIClient.kt | 7 ++++++ .../storage/BaseCredentialsManager.kt | 4 ++++ .../storage/CredentialsManagerException.kt | 9 ++++++++ .../main/java/com/auth0/android/dpop/DPoP.kt | 23 +++++++++++++++++++ 4 files changed, 43 insertions(+) diff --git a/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt b/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt index 572ecc51..f33aee9c 100755 --- a/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt @@ -55,6 +55,13 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe private var dPoP: DPoP? = null + /** + * Returns whether DPoP (Demonstrating Proof of Possession) is enabled on this client. + * DPoP is enabled by calling [useDPoP]. + */ + public val isDPoPEnabled: Boolean + get() = dPoP != null + /** * Creates a new API client instance providing Auth0 account info. * diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt index d3ac32d5..c7237586 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt @@ -20,6 +20,10 @@ public abstract class BaseCredentialsManager internal constructor( protected val storage: Storage, private val jwtDecoder: JWTDecoder ) { + + internal companion object { + internal const val KEY_DPOP_THUMBPRINT = "com.auth0.dpop_key_thumbprint" + } private var _clock: Clock = ClockImpl() /** diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt index 9796dbe6..d1fa9a5e 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt @@ -48,6 +48,8 @@ public class CredentialsManagerException : API_ERROR, SSO_EXCHANGE_FAILED, MFA_REQUIRED, + DPOP_KEY_MISSING, + DPOP_NOT_CONFIGURED, UNKNOWN_ERROR } @@ -159,6 +161,11 @@ public class CredentialsManagerException : public val MFA_REQUIRED: CredentialsManagerException = CredentialsManagerException(Code.MFA_REQUIRED) + public val DPOP_KEY_MISSING: CredentialsManagerException = + CredentialsManagerException(Code.DPOP_KEY_MISSING) + public val DPOP_NOT_CONFIGURED: CredentialsManagerException = + CredentialsManagerException(Code.DPOP_NOT_CONFIGURED) + public val UNKNOWN_ERROR: CredentialsManagerException = CredentialsManagerException(Code.UNKNOWN_ERROR) @@ -207,6 +214,8 @@ public class CredentialsManagerException : Code.API_ERROR -> "An error occurred while processing the request." Code.SSO_EXCHANGE_FAILED ->"The exchange of the refresh token for SSO credentials failed." Code.MFA_REQUIRED -> "Multi-factor authentication is required to complete the credential renewal." + Code.DPOP_KEY_MISSING -> "The stored credentials are DPoP-bound but the DPoP key pair is no longer available in the Android KeyStore. Re-authentication is required." + Code.DPOP_NOT_CONFIGURED -> "The stored credentials are DPoP-bound but the AuthenticationAPIClient used by this CredentialsManager was not configured with useDPoP(context). Call AuthenticationAPIClient(auth0).useDPoP(context) and pass the configured client to CredentialsManager." Code.UNKNOWN_ERROR -> "An unknown error has occurred while fetching the token. Please check the error cause for more details." } } diff --git a/auth0/src/main/java/com/auth0/android/dpop/DPoP.kt b/auth0/src/main/java/com/auth0/android/dpop/DPoP.kt index d84f7f61..b0490575 100644 --- a/auth0/src/main/java/com/auth0/android/dpop/DPoP.kt +++ b/auth0/src/main/java/com/auth0/android/dpop/DPoP.kt @@ -198,6 +198,29 @@ public class DPoP(context: Context) { return HeaderData(token, proof) } + /** + * Returns whether a DPoP key pair currently exists in the Android KeyStore. + * + * This can be used to check if DPoP credentials are still available after events + * like device backup/restore or factory reset, which do not preserve KeyStore entries. + * + * ```kotlin + * + * if (!DPoP.hasKeyPair()) { + * // Key was lost — clear stored credentials and re-authenticate + * } + * + * ``` + * + * @return true if a DPoP key pair exists in the KeyStore, false otherwise. + * @throws DPoPException if there is an error accessing the KeyStore. + */ + @Throws(DPoPException::class) + @JvmStatic + public fun hasKeyPair(): Boolean { + return DPoPUtil.hasKeyPair() + } + /** * Method to clear the DPoP key pair from the keystore. It must be called when the user logs out * to prevent reuse of the key pair in subsequent sessions. From dac4dce02b5f0f3a4a2ce3cbcaf89f02245862d1 Mon Sep 17 00:00:00 2001 From: Prince Mathew Date: Mon, 23 Mar 2026 16:45:56 +0530 Subject: [PATCH 4/6] Saving the Dpop thumbprint --- .../storage/BaseCredentialsManager.kt | 25 +++++++++++++++++++ .../storage/CredentialsManager.kt | 2 ++ .../storage/SecureCredentialsManager.kt | 7 ++++++ 3 files changed, 34 insertions(+) diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt index c7237586..e6be3340 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt @@ -4,6 +4,8 @@ import android.util.Log import androidx.annotation.VisibleForTesting import com.auth0.android.authentication.AuthenticationAPIClient import com.auth0.android.callback.Callback +import com.auth0.android.dpop.DPoPException +import com.auth0.android.dpop.DPoPUtil import com.auth0.android.result.APICredentials import com.auth0.android.result.Credentials import com.auth0.android.result.SSOCredentials @@ -159,6 +161,29 @@ public abstract class BaseCredentialsManager internal constructor( internal val currentTimeInMillis: Long get() = _clock.getCurrentTimeMillis() + /** + * Stores the DPoP key thumbprint if DPoP was used for this credential set. + * Uses a dual strategy to store the thumbprint: + * - credentials.type == "DPoP" when server confirms DPoP but client lacks useDPoP() + * - isDPoPEnabled catches the case where client used DPoP, server returned token_type: "Bearer" + */ + protected fun saveDPoPThumbprint(credentials: Credentials) { + val dpopUsed = credentials.type.equals("DPoP", ignoreCase = true) + || authenticationClient.isDPoPEnabled + if (dpopUsed && DPoPUtil.hasKeyPair()) { + try { + val thumbprint = DPoPUtil.getPublicKeyJWK() + if (thumbprint != null) { + storage.store(KEY_DPOP_THUMBPRINT, thumbprint) + } + } catch (e: DPoPException) { + Log.w(this::class.java.simpleName, "Failed to store DPoP key thumbprint", e) + } + } else { + storage.remove(KEY_DPOP_THUMBPRINT) + } + } + /** * Checks if the stored scope is the same as the requested one. * 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 0cc5c61f..a658f9b8 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 @@ -75,6 +75,7 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting storage.store(KEY_EXPIRES_AT, credentials.expiresAt.time) storage.store(KEY_SCOPE, credentials.scope) storage.store(LEGACY_KEY_CACHE_EXPIRES_AT, credentials.expiresAt.time) + saveDPoPThumbprint(credentials) } /** @@ -714,6 +715,7 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting storage.remove(KEY_EXPIRES_AT) storage.remove(KEY_SCOPE) storage.remove(LEGACY_KEY_CACHE_EXPIRES_AT) + storage.remove(KEY_DPOP_THUMBPRINT) } /** 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 4a367e77..41c05447 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 @@ -189,6 +189,8 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT ) storage.store(LEGACY_KEY_CACHE_EXPIRES_AT, credentials.expiresAt.time) storage.store(KEY_CAN_REFRESH, canRefresh) + storage.store(KEY_TOKEN_TYPE, credentials.type) + saveDPoPThumbprint(credentials) } catch (e: IncompatibleDeviceException) { throw CredentialsManagerException( CredentialsManagerException.Code.INCOMPATIBLE_DEVICE, e @@ -735,6 +737,8 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT storage.remove(KEY_EXPIRES_AT) storage.remove(LEGACY_KEY_CACHE_EXPIRES_AT) storage.remove(KEY_CAN_REFRESH) + storage.remove(KEY_TOKEN_TYPE) + storage.remove(KEY_DPOP_THUMBPRINT) clearBiometricSession() Log.d(TAG, "Credentials were just removed from the storage") } @@ -1251,6 +1255,9 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal const val KEY_ALIAS = "com.auth0.key" + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal const val KEY_TOKEN_TYPE = "com.auth0.token_type" + // Using NO_SESSION to represent "no session" (uninitialized state) private const val NO_SESSION = -1L } From 5875c0ce2507e6872c14ea4ea5871d2997ef5a7c Mon Sep 17 00:00:00 2001 From: Prince Mathew Date: Tue, 24 Mar 2026 15:15:16 +0530 Subject: [PATCH 5/6] Addressed review comments --- .../storage/BaseCredentialsManager.kt | 24 ++++++++++++------- .../storage/CredentialsManagerException.kt | 2 +- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt index e6be3340..0d34432f 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt @@ -170,15 +170,21 @@ public abstract class BaseCredentialsManager internal constructor( protected fun saveDPoPThumbprint(credentials: Credentials) { val dpopUsed = credentials.type.equals("DPoP", ignoreCase = true) || authenticationClient.isDPoPEnabled - if (dpopUsed && DPoPUtil.hasKeyPair()) { - try { - val thumbprint = DPoPUtil.getPublicKeyJWK() - if (thumbprint != null) { - storage.store(KEY_DPOP_THUMBPRINT, thumbprint) - } - } catch (e: DPoPException) { - Log.w(this::class.java.simpleName, "Failed to store DPoP key thumbprint", e) - } + + if (!dpopUsed) { + storage.remove(KEY_DPOP_THUMBPRINT) + return + } + + val thumbprint = try { + if (DPoPUtil.hasKeyPair()) DPoPUtil.getPublicKeyJWK() else null + } catch (e: DPoPException) { + Log.w(this::class.java.simpleName, "Failed to fetch DPoP key thumbprint", e) + null + } + + if (thumbprint != null) { + storage.store(KEY_DPOP_THUMBPRINT, thumbprint) } else { storage.remove(KEY_DPOP_THUMBPRINT) } diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt index d1fa9a5e..c141ae83 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt @@ -215,7 +215,7 @@ public class CredentialsManagerException : Code.SSO_EXCHANGE_FAILED ->"The exchange of the refresh token for SSO credentials failed." Code.MFA_REQUIRED -> "Multi-factor authentication is required to complete the credential renewal." Code.DPOP_KEY_MISSING -> "The stored credentials are DPoP-bound but the DPoP key pair is no longer available in the Android KeyStore. Re-authentication is required." - Code.DPOP_NOT_CONFIGURED -> "The stored credentials are DPoP-bound but the AuthenticationAPIClient used by this CredentialsManager was not configured with useDPoP(context). Call AuthenticationAPIClient(auth0).useDPoP(context) and pass the configured client to CredentialsManager." + Code.DPOP_NOT_CONFIGURED -> "The stored credentials are DPoP-bound but the AuthenticationAPIClient used by this credentials manager was not configured with useDPoP(context). Call AuthenticationAPIClient(auth0).useDPoP(context) and pass the configured client to the credentials manager." Code.UNKNOWN_ERROR -> "An unknown error has occurred while fetching the token. Please check the error cause for more details." } } From 97fd1b199b0eb05f8eb7c3c3e3353500ea6e44ab Mon Sep 17 00:00:00 2001 From: Prince Mathew Date: Tue, 24 Mar 2026 21:47:14 +0530 Subject: [PATCH 6/6] Moved the token type key to BaseCredentialsManager class --- .../authentication/storage/BaseCredentialsManager.kt | 6 +++++- .../android/authentication/storage/CredentialsManager.kt | 1 - .../authentication/storage/SecureCredentialsManager.kt | 8 ++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt index 0d34432f..384265cc 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt @@ -25,7 +25,11 @@ public abstract class BaseCredentialsManager internal constructor( internal companion object { internal const val KEY_DPOP_THUMBPRINT = "com.auth0.dpop_key_thumbprint" + + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + internal const val KEY_TOKEN_TYPE = "com.auth0.token_type" } + private var _clock: Clock = ClockImpl() /** @@ -169,7 +173,7 @@ public abstract class BaseCredentialsManager internal constructor( */ protected fun saveDPoPThumbprint(credentials: Credentials) { val dpopUsed = credentials.type.equals("DPoP", ignoreCase = true) - || authenticationClient.isDPoPEnabled + || authenticationClient.isDPoPEnabled if (!dpopUsed) { storage.remove(KEY_DPOP_THUMBPRINT) 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 a658f9b8..4cc3144b 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 @@ -763,7 +763,6 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting private const val KEY_ACCESS_TOKEN = "com.auth0.access_token" private const val KEY_REFRESH_TOKEN = "com.auth0.refresh_token" private const val KEY_ID_TOKEN = "com.auth0.id_token" - private const val KEY_TOKEN_TYPE = "com.auth0.token_type" private const val KEY_EXPIRES_AT = "com.auth0.expires_at" private const val KEY_SCOPE = "com.auth0.scope" 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 41c05447..adcb883a 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 @@ -897,7 +897,8 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT callback.onFailure( CredentialsManagerException( CredentialsManagerException.Code.MFA_REQUIRED, - error.message ?: "Multi-factor authentication is required to complete the credential renewal.", + error.message + ?: "Multi-factor authentication is required to complete the credential renewal.", error, error.mfaRequiredErrorPayload ) @@ -1055,7 +1056,8 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT callback.onFailure( CredentialsManagerException( CredentialsManagerException.Code.MFA_REQUIRED, - error.message ?: "Multi-factor authentication is required to complete the credential renewal.", + error.message + ?: "Multi-factor authentication is required to complete the credential renewal.", error, error.mfaRequiredErrorPayload ) @@ -1255,8 +1257,6 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal const val KEY_ALIAS = "com.auth0.key" - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal const val KEY_TOKEN_TYPE = "com.auth0.token_type" // Using NO_SESSION to represent "no session" (uninitialized state) private const val NO_SESSION = -1L