Skip to content

Commit 6c1fada

Browse files
committed
Merge origin/v4_development into update-min-sdk-26
2 parents a998e50 + 9838b8b commit 6c1fada

8 files changed

Lines changed: 121 additions & 4 deletions

File tree

V4_MIGRATION_GUIDE.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ v4 of the Auth0 Android SDK includes significant build toolchain updates, update
2323
- [**Behavior Changes**](#behavior-changes)
2424
+ [clearCredentials() Now Clears All Storage](#clearCredentials-now-clears-all-storage)
2525
+ [Storage Interface: New removeAll() Method](#storage-interface-new-removeall-method)
26+
- [**New APIs**](#new-apis)
27+
+ [clearAll() — Full Credential and Key Cleanup](#clearall--full-credential-and-key-cleanup)
2628
- [**Dependency Changes**](#dependency-changes)
2729
+ [Gson 2.8.9 → 2.11.0](#️-gson-289--2110-transitive-dependency)
2830
+ [DefaultClient.Builder](#defaultclientbuilder)
@@ -245,6 +247,26 @@ In v4, `clearCredentials()` calls `Storage.removeAll()`, which clears **all** va
245247

246248
**Impact:** Existing custom `Storage` implementations will continue to compile and work without changes. Override `removeAll()` to provide the actual clearing behavior if your custom storage is used with `clearCredentials()`.
247249

250+
## New APIs
251+
252+
### `clearAll()` — Full Credential and Key Cleanup
253+
254+
v4 introduces a new `clearAll()` method on `CredentialsManager` and `SecureCredentialsManager` that performs a complete cleanup of all stored credentials **and** cryptographic key pairs.
255+
256+
**Usage:**
257+
258+
```kotlin
259+
// Clear everything on logout — credentials, DPoP keys, and encryption keys
260+
credentialsManager.clearAll()
261+
```
262+
263+
**When to use `clearAll()` vs `clearCredentials()`:**
264+
265+
- Use **`clearCredentials()`** when you only need to remove stored tokens (e.g., forcing a re-login) but want to preserve cryptographic keys for future sessions.
266+
- Use **`clearAll()`** on full logout or account removal, when you want to ensure no credentials or key material remain on the device.
267+
268+
> **Note:** `clearAll()` catches any errors from DPoP key pair deletion internally, so it will not throw even if the DPoP key pair was never created or has already been removed.
269+
248270
## Dependency Changes
249271

250272
### ⚠️ Gson 2.8.9 → 2.11.0 (Transitive Dependency)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ public abstract class BaseCredentialsManager internal constructor(
159159

160160
public abstract fun clearCredentials()
161161
public abstract fun clearApiCredentials(audience: String, scope: String? = null)
162+
public abstract fun clearAll()
162163
public abstract fun hasValidCredentials(): Boolean
163164
public abstract fun hasValidCredentials(minTtl: Long): Boolean
164165

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import androidx.annotation.VisibleForTesting
66
import com.auth0.android.authentication.AuthenticationAPIClient
77
import com.auth0.android.authentication.AuthenticationException
88
import com.auth0.android.callback.Callback
9+
import com.auth0.android.dpop.DPoP
10+
import com.auth0.android.dpop.DPoPException
911
import com.auth0.android.request.internal.GsonProvider
1012
import com.auth0.android.request.internal.Jwt
1113
import com.auth0.android.result.APICredentials
@@ -528,7 +530,8 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
528530
callback.onFailure(
529531
CredentialsManagerException(
530532
CredentialsManagerException.Code.MFA_REQUIRED,
531-
error.message ?: "Multi-factor authentication is required to complete the credential renewal.",
533+
error.message
534+
?: "Multi-factor authentication is required to complete the credential renewal.",
532535
error,
533536
error.mfaRequiredErrorPayload
534537
)
@@ -654,7 +657,8 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
654657
callback.onFailure(
655658
CredentialsManagerException(
656659
CredentialsManagerException.Code.MFA_REQUIRED,
657-
error.message ?: "Multi-factor authentication is required to complete the credential renewal.",
660+
error.message
661+
?: "Multi-factor authentication is required to complete the credential renewal.",
658662
error,
659663
error.mfaRequiredErrorPayload
660664
)
@@ -710,6 +714,19 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
710714
storage.removeAll()
711715
}
712716

717+
/**
718+
* Removes all credentials, API credentials, and cryptographic key pairs.
719+
* This calls [Storage.removeAll] to clear all stored data
720+
*/
721+
override fun clearAll() {
722+
storage.removeAll()
723+
try {
724+
DPoP.clearKeyPair()
725+
} catch (e: DPoPException) {
726+
Log.e(TAG, "Failed to clear DPoP key pair ${e.stackTraceToString()}")
727+
}
728+
}
729+
713730
/**
714731
* Removes the credentials for the given audience from the storage if present.
715732
* @param audience Audience for which the [APICredentials] are stored

auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,14 @@ private void deleteAESKeys() {
299299
storage.remove(OLD_KEY_IV_ALIAS);
300300
}
301301

302+
/**
303+
* Removes all cryptographic keys (both RSA and AES) used by this instance.
304+
*/
305+
public void deleteAllKeys() {
306+
deleteRSAKeys();
307+
deleteAESKeys();
308+
}
309+
302310
/**
303311
* Decrypts the given input using a generated RSA Private Key.
304312
* Used to decrypt the AES key for later usage.

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import com.auth0.android.Auth0
1010
import com.auth0.android.authentication.AuthenticationAPIClient
1111
import com.auth0.android.authentication.AuthenticationException
1212
import com.auth0.android.callback.Callback
13+
import com.auth0.android.dpop.DPoP
14+
import com.auth0.android.dpop.DPoPException
1315
import com.auth0.android.request.internal.GsonProvider
1416
import com.auth0.android.result.APICredentials
1517
import com.auth0.android.result.Credentials
@@ -736,6 +738,22 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT
736738
Log.d(TAG, "Credentials were just removed from the storage")
737739
}
738740

741+
/**
742+
* Removes all credentials, API credentials, and cryptographic key pairs.
743+
* This calls [Storage.removeAll] to clear all stored data
744+
*/
745+
override fun clearAll() {
746+
storage.removeAll()
747+
crypto.deleteAllKeys()
748+
clearBiometricSession()
749+
try {
750+
DPoP.clearKeyPair()
751+
} catch (e: DPoPException) {
752+
Log.e(TAG, "Failed to clear DPoP key pair ${e.stackTraceToString()}")
753+
}
754+
Log.d(TAG, "All credentials and key pairs were removed")
755+
}
756+
739757
/**
740758
* Removes the credentials for the given audience from the storage if present.
741759
* @param audience Audience for which the [APICredentials] are stored
@@ -890,7 +908,8 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT
890908
callback.onFailure(
891909
CredentialsManagerException(
892910
CredentialsManagerException.Code.MFA_REQUIRED,
893-
error.message ?: "Multi-factor authentication is required to complete the credential renewal.",
911+
error.message
912+
?: "Multi-factor authentication is required to complete the credential renewal.",
894913
error,
895914
error.mfaRequiredErrorPayload
896915
)
@@ -1048,7 +1067,8 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT
10481067
callback.onFailure(
10491068
CredentialsManagerException(
10501069
CredentialsManagerException.Code.MFA_REQUIRED,
1051-
error.message ?: "Multi-factor authentication is required to complete the credential renewal.",
1070+
error.message
1071+
?: "Multi-factor authentication is required to complete the credential renewal.",
10521072
error,
10531073
error.mfaRequiredErrorPayload
10541074
)

auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,6 +1495,12 @@ public class CredentialsManagerTest {
14951495
verifyNoMoreInteractions(storage)
14961496
}
14971497

1498+
@Test
1499+
public fun shouldClearAllCredentials() {
1500+
manager.clearAll()
1501+
verify(storage).removeAll()
1502+
}
1503+
14981504
@Test
14991505
public fun shouldSaveApiCredentialsWithScopeAsKey() {
15001506
val expirationTime = CredentialsMock.ONE_HOUR_AHEAD_MS

auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1951,6 +1951,41 @@ public void shouldNotPropagateProviderExceptionAsIncompatibleDeviceException() t
19511951
assertThat(result, is(newAESKey));
19521952
}
19531953

1954+
/*
1955+
* deleteAllKeys() tests
1956+
*/
1957+
1958+
@Test
1959+
public void shouldDeleteBothRSAAndAESKeysWhenDeleteAllKeysIsCalled() throws Exception {
1960+
cryptoUtil.deleteAllKeys();
1961+
1962+
// Verify RSA keys deleted from KeyStore
1963+
Mockito.verify(keyStore).deleteEntry(KEY_ALIAS);
1964+
Mockito.verify(keyStore).deleteEntry(OLD_KEY_ALIAS);
1965+
1966+
// Verify AES keys deleted from Storage
1967+
Mockito.verify(storage).remove(KEY_ALIAS);
1968+
Mockito.verify(storage).remove(KEY_ALIAS + "_iv");
1969+
Mockito.verify(storage).remove(OLD_KEY_ALIAS);
1970+
Mockito.verify(storage).remove(OLD_KEY_ALIAS + "_iv");
1971+
}
1972+
1973+
@Test
1974+
public void shouldDeleteAESKeysEvenIfRSAKeyDeletionFails() throws Exception {
1975+
doThrow(new KeyStoreException("KeyStore error")).when(keyStore).deleteEntry(anyString());
1976+
1977+
cryptoUtil.deleteAllKeys();
1978+
1979+
// RSA deletion was attempted (first deleteEntry throws, second is never reached)
1980+
Mockito.verify(keyStore).deleteEntry(KEY_ALIAS);
1981+
1982+
// AES keys should still be deleted from Storage
1983+
Mockito.verify(storage).remove(KEY_ALIAS);
1984+
Mockito.verify(storage).remove(KEY_ALIAS + "_iv");
1985+
Mockito.verify(storage).remove(OLD_KEY_ALIAS);
1986+
Mockito.verify(storage).remove(OLD_KEY_ALIAS + "_iv");
1987+
}
1988+
19541989
/*
19551990
* Helper methods
19561991
*/

auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2164,6 +2164,14 @@ public class SecureCredentialsManagerTest {
21642164
verifyNoMoreInteractions(storage)
21652165
}
21662166

2167+
@Test
2168+
public fun shouldClearAllCredentialsKeyPairsAndBiometricSession() {
2169+
manager.clearAll()
2170+
verify(storage).removeAll()
2171+
verify(crypto).deleteAllKeys()
2172+
Assert.assertFalse(manager.isBiometricSessionValid())
2173+
}
2174+
21672175
@Test
21682176
public fun shouldSaveEncryptedApiCredentialsWithScopeAsKey() {
21692177
val expirationTime = CredentialsMock.ONE_HOUR_AHEAD_MS

0 commit comments

Comments
 (0)