Skip to content

Commit 29c94ab

Browse files
authored
Store the Dpop thumbprint to local storage (#940)
1 parent ec2c6ed commit 29c94ab

File tree

4 files changed

+47
-4
lines changed

4 files changed

+47
-4
lines changed

auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import android.util.Log
44
import androidx.annotation.VisibleForTesting
55
import com.auth0.android.authentication.AuthenticationAPIClient
66
import com.auth0.android.callback.Callback
7+
import com.auth0.android.dpop.DPoPException
8+
import com.auth0.android.dpop.DPoPUtil
79
import com.auth0.android.result.APICredentials
810
import com.auth0.android.result.Credentials
911
import com.auth0.android.result.SSOCredentials
@@ -23,7 +25,11 @@ public abstract class BaseCredentialsManager internal constructor(
2325

2426
internal companion object {
2527
internal const val KEY_DPOP_THUMBPRINT = "com.auth0.dpop_key_thumbprint"
28+
29+
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
30+
internal const val KEY_TOKEN_TYPE = "com.auth0.token_type"
2631
}
32+
2733
private var _clock: Clock = ClockImpl()
2834

2935
/**
@@ -159,6 +165,35 @@ public abstract class BaseCredentialsManager internal constructor(
159165
internal val currentTimeInMillis: Long
160166
get() = _clock.getCurrentTimeMillis()
161167

168+
/**
169+
* Stores the DPoP key thumbprint if DPoP was used for this credential set.
170+
* Uses a dual strategy to store the thumbprint:
171+
* - credentials.type == "DPoP" when server confirms DPoP but client lacks useDPoP()
172+
* - isDPoPEnabled catches the case where client used DPoP, server returned token_type: "Bearer"
173+
*/
174+
protected fun saveDPoPThumbprint(credentials: Credentials) {
175+
val dpopUsed = credentials.type.equals("DPoP", ignoreCase = true)
176+
|| authenticationClient.isDPoPEnabled
177+
178+
if (!dpopUsed) {
179+
storage.remove(KEY_DPOP_THUMBPRINT)
180+
return
181+
}
182+
183+
val thumbprint = try {
184+
if (DPoPUtil.hasKeyPair()) DPoPUtil.getPublicKeyJWK() else null
185+
} catch (e: DPoPException) {
186+
Log.w(this::class.java.simpleName, "Failed to fetch DPoP key thumbprint", e)
187+
null
188+
}
189+
190+
if (thumbprint != null) {
191+
storage.store(KEY_DPOP_THUMBPRINT, thumbprint)
192+
} else {
193+
storage.remove(KEY_DPOP_THUMBPRINT)
194+
}
195+
}
196+
162197
/**
163198
* Checks if the stored scope is the same as the requested one.
164199
*

auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
7575
storage.store(KEY_EXPIRES_AT, credentials.expiresAt.time)
7676
storage.store(KEY_SCOPE, credentials.scope)
7777
storage.store(LEGACY_KEY_CACHE_EXPIRES_AT, credentials.expiresAt.time)
78+
saveDPoPThumbprint(credentials)
7879
}
7980

8081
/**
@@ -714,6 +715,7 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
714715
storage.remove(KEY_EXPIRES_AT)
715716
storage.remove(KEY_SCOPE)
716717
storage.remove(LEGACY_KEY_CACHE_EXPIRES_AT)
718+
storage.remove(KEY_DPOP_THUMBPRINT)
717719
}
718720

719721
/**
@@ -761,7 +763,6 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
761763
private const val KEY_ACCESS_TOKEN = "com.auth0.access_token"
762764
private const val KEY_REFRESH_TOKEN = "com.auth0.refresh_token"
763765
private const val KEY_ID_TOKEN = "com.auth0.id_token"
764-
private const val KEY_TOKEN_TYPE = "com.auth0.token_type"
765766
private const val KEY_EXPIRES_AT = "com.auth0.expires_at"
766767
private const val KEY_SCOPE = "com.auth0.scope"
767768

auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ public class CredentialsManagerException :
215215
Code.SSO_EXCHANGE_FAILED ->"The exchange of the refresh token for SSO credentials failed."
216216
Code.MFA_REQUIRED -> "Multi-factor authentication is required to complete the credential renewal."
217217
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."
218-
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."
218+
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."
219219
Code.UNKNOWN_ERROR -> "An unknown error has occurred while fetching the token. Please check the error cause for more details."
220220
}
221221
}

auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT
189189
)
190190
storage.store(LEGACY_KEY_CACHE_EXPIRES_AT, credentials.expiresAt.time)
191191
storage.store(KEY_CAN_REFRESH, canRefresh)
192+
storage.store(KEY_TOKEN_TYPE, credentials.type)
193+
saveDPoPThumbprint(credentials)
192194
} catch (e: IncompatibleDeviceException) {
193195
throw CredentialsManagerException(
194196
CredentialsManagerException.Code.INCOMPATIBLE_DEVICE, e
@@ -735,6 +737,8 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT
735737
storage.remove(KEY_EXPIRES_AT)
736738
storage.remove(LEGACY_KEY_CACHE_EXPIRES_AT)
737739
storage.remove(KEY_CAN_REFRESH)
740+
storage.remove(KEY_TOKEN_TYPE)
741+
storage.remove(KEY_DPOP_THUMBPRINT)
738742
clearBiometricSession()
739743
Log.d(TAG, "Credentials were just removed from the storage")
740744
}
@@ -893,7 +897,8 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT
893897
callback.onFailure(
894898
CredentialsManagerException(
895899
CredentialsManagerException.Code.MFA_REQUIRED,
896-
error.message ?: "Multi-factor authentication is required to complete the credential renewal.",
900+
error.message
901+
?: "Multi-factor authentication is required to complete the credential renewal.",
897902
error,
898903
error.mfaRequiredErrorPayload
899904
)
@@ -1051,7 +1056,8 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT
10511056
callback.onFailure(
10521057
CredentialsManagerException(
10531058
CredentialsManagerException.Code.MFA_REQUIRED,
1054-
error.message ?: "Multi-factor authentication is required to complete the credential renewal.",
1059+
error.message
1060+
?: "Multi-factor authentication is required to complete the credential renewal.",
10551061
error,
10561062
error.mfaRequiredErrorPayload
10571063
)
@@ -1251,6 +1257,7 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT
12511257
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
12521258
internal const val KEY_ALIAS = "com.auth0.key"
12531259

1260+
12541261
// Using NO_SESSION to represent "no session" (uninitialized state)
12551262
private const val NO_SESSION = -1L
12561263
}

0 commit comments

Comments
 (0)