Repository: auth0/auth0.android
Issue: Credentials lost after update

### Description

We just released a new version of our app (upgrading to Android X) and it seems like every user has lost their credentials:

> A change on the Lock Screen security settings have deemed the encryption keys invalid and have been recreated. Any previously stored content is now lost. Please, try saving the credentials again.

It's possible this is our fault, but the cause seems very opaque to us. Is it possible that the error message is catching other types of error as well?

https://github.com/auth0/Auth0.Android/blob/dc19c45c01fd5a5d7d023755dc96432ff63ee284/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.java#L170-L178

### Reproduction

- Follow examples for `SecureCredentialsManager` 
- Update app (unclear what changes cause this problem)

Thanks
Adam


Changed files:
auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.java
auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.java

diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.java b/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.java
index 2fde37d..3b2bbf9 100644
--- a/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.java
+++ b/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.java
@@ -41,7 +41,9 @@ public class SecureCredentialsManager {
     private static final String KEY_CREDENTIALS = "com.auth0.credentials";
     private static final String KEY_EXPIRES_AT = "com.auth0.credentials_expires_at";
     private static final String KEY_CAN_REFRESH = "com.auth0.credentials_can_refresh";
-    private static final String KEY_ALIAS = "com.auth0.key";
+    private static final String KEY_CRYPTO_ALIAS = "com.auth0.manager_key_alias";
+    @VisibleForTesting
+    static final String KEY_ALIAS = "com.auth0.key";
 
     private final AuthenticationAPIClient apiClient;
     private final Storage storage;
@@ -165,6 +167,7 @@ public class SecureCredentialsManager {
             storage.store(KEY_CREDENTIALS, encryptedEncoded);
             storage.store(KEY_EXPIRES_AT, expiresAt);
             storage.store(KEY_CAN_REFRESH, canRefresh);
+            storage.store(KEY_CRYPTO_ALIAS, KEY_ALIAS);
         } catch (IncompatibleDeviceException e) {
             throw new CredentialsManagerException(String.format("This device is not compatible with the %s class.", SecureCredentialsManager.class.getSimpleName()), e);
         } catch (CryptoException e) {
@@ -211,6 +214,7 @@ public class SecureCredentialsManager {
         storage.remove(KEY_CREDENTIALS);
         storage.remove(KEY_EXPIRES_AT);
         storage.remove(KEY_CAN_REFRESH);
+        storage.remove(KEY_CRYPTO_ALIAS);
         Log.d(TAG, "Credentials were just removed from the storage");
     }
 
@@ -223,9 +227,10 @@ public class SecureCredentialsManager {
         String encryptedEncoded = storage.retrieveString(KEY_CREDENTIALS);
         Long expiresAt = storage.retrieveLong(KEY_EXPIRES_AT);
         Boolean canRefresh = storage.retrieveBoolean(KEY_CAN_REFRESH);
-        return !(isEmpty(encryptedEncoded) ||
-                expiresAt == null ||
-                expiresAt <= getCurrentTimeInMillis() && (canRefresh == null || !canRefresh));
+        String keyAliasUsed = storage.retrieveString(KEY_CRYPTO_ALIAS);
+        return KEY_ALIAS.equals(keyAliasUsed) &&
+                !(isEmpty(encryptedEncoded) || expiresAt == null ||
+                        expiresAt <= getCurrentTimeInMillis() && (canRefresh == null || !canRefresh));
     }
 
     private void continueGetCredentials(final BaseCallback<Credentials, CredentialsManagerException> callback) {
diff --git a/auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.java b/auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.java
index cc68457..389b71e 100644
--- a/auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.java
+++ b/auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.java
@@ -128,6 +128,7 @@ public class SecureCredentialsManagerTest {
         verify(storage).store(eq("com.auth0.credentials"), stringCaptor.capture());
         verify(storage).store("com.auth0.credentials_expires_at", sharedExpirationTime);
         verify(storage).store("com.auth0.credentials_can_refresh", true);
+        verify(storage).store("com.auth0.manager_key_alias", SecureCredentialsManager.KEY_ALIAS);
         verifyNoMoreInteractions(storage);
         final String encodedJson = stringCaptor.getValue();
         assertThat(encodedJson, is(notNullValue()));
@@ -155,6 +156,7 @@ public class SecureCredentialsManagerTest {
         verify(storage).store(eq("com.auth0.credentials"), stringCaptor.capture());
         verify(storage).store("com.auth0.credentials_expires_at", accessTokenExpirationTime);
         verify(storage).store("com.auth0.credentials_can_refresh", true);
+        verify(storage).store("com.auth0.manager_key_alias", SecureCredentialsManager.KEY_ALIAS);
         verifyNoMoreInteractions(storage);
         final String encodedJson = stringCaptor.getValue();
         assertThat(encodedJson, is(notNullValue()));
@@ -183,6 +185,7 @@ public class SecureCredentialsManagerTest {
         verify(storage).store(eq("com.auth0.credentials"), stringCaptor.capture());
         verify(storage).store("com.auth0.credentials_expires_at", idTokenExpirationTime);
         verify(storage).store("com.auth0.credentials_can_refresh", true);
+        verify(storage).store("com.auth0.manager_key_alias", SecureCredentialsManager.KEY_ALIAS);
         verifyNoMoreInteractions(storage);
         final String encodedJson = stringCaptor.getValue();
         assertThat(encodedJson, is(notNullValue()));
@@ -210,6 +213,7 @@ public class SecureCredentialsManagerTest {
         verify(storage).store(eq("com.auth0.credentials"), stringCaptor.capture());
         verify(storage).store("com.auth0.credentials_expires_at", expirationTime);
         verify(storage).store("com.auth0.credentials_can_refresh", false);
+        verify(storage).store("com.auth0.manager_key_alias", SecureCredentialsManager.KEY_ALIAS);
         verifyNoMoreInteractions(storage);
         final String encodedJson = stringCaptor.getValue();
         assertThat(encodedJson, is(notNullValue()));
@@ -573,6 +577,7 @@ public class SecureCredentialsManagerTest {
         verify(storage).remove("com.auth0.credentials");
         verify(storage).remove("com.auth0.credentials_expires_at");
         verify(storage).remove("com.auth0.credentials_can_refresh");
+        verify(storage).remove("com.auth0.manager_key_alias");
         verifyNoMoreInteractions(storage);
     }
 
@@ -586,6 +591,7 @@ public class SecureCredentialsManagerTest {
         when(storage.retrieveLong("com.auth0.credentials_expires_at")).thenReturn(expirationTime);
         when(storage.retrieveBoolean("com.auth0.credentials_can_refresh")).thenReturn(false);
         when(storage.retrieveString("com.auth0.credentials")).thenReturn("{\\"id_token\\":\\"idToken\\"}");
+        when(storage.retrieveString("com.auth0.manager_key_alias")).thenReturn(SecureCredentialsManager.KEY_ALIAS);
         assertThat(manager.hasValidCredentials(), is(true));
 
         when(storage.retrieveString("com.auth0.credentials")).thenReturn("{\\"access_token\\":\\"accessToken\\"}");
@@ -598,6 +604,7 @@ public class SecureCredentialsManagerTest {
         when(storage.retrieveLong("com.auth0.credentials_expires_at")).thenReturn(expirationTime);
         when(storage.retrieveBoolean("com.auth0.credentials_can_refresh")).thenReturn(false);
         when(storage.retrieveString("com.auth0.credentials")).thenReturn("{\\"id_token\\":\\"idToken\\"}");
+        when(storage.retrieveString("com.auth0.manager_key_alias")).thenReturn(SecureCredentialsManager.KEY_ALIAS);
         assertThat(manager.hasValidCredentials(), is(false));
 
         when(storage.retrieveString("com.auth0.credentials")).thenReturn("{\\"access_token\\":\\"accessToken\\"}");
@@ -610,6 +617,7 @@ public class SecureCredentialsManagerTest {
         when(storage.retrieveLong("com.auth0.credentials_expires_at")).thenReturn(expirationTime);
         when(storage.retrieveBoolean("com.auth0.credentials_can_refresh")).thenReturn(true);
         when(storage.retrieveString("com.auth0.credentials")).thenReturn("{\\"id_token\\":\\"idToken\\", \\"refresh_token\\":\\"refreshToken\\"}");
+        when(storage.retrieveString("com.auth0.manager_key_alias")).thenReturn(SecureCredentialsManager.KEY_ALIAS);
         assertThat(manager.hasValidCredentials(), is(true));
 
         when(storage.retrieveString("com.auth0.credentials")).thenReturn("{\\"access_token\\":\\"accessToken\\", \\"refresh_token\\":\\"refreshToken\\"}");
@@ -619,10 +627,37 @@ public class SecureCredentialsManagerTest {
     @Test
     public void shouldNotHaveCredentialsWhenAccessTokenAndIdTokenAreMissing() {
         when(storage.retrieveString("com.auth0.credentials")).thenReturn("{\\"token_type\\":\\"type\\", \\"refresh_token\\":\\"refreshToken\\"}");
+        when(storage.retrieveString("com.auth0.manager_key_alias")).thenReturn(SecureCredentialsManager.KEY_ALIAS);
 
         assertFalse(manager.hasValidCredentials());
     }
 
+    @Test
+    public void shouldNotHaveCredentialsWhenTheAliasUsedHasNotBeenMigratedYet() {
+        long expirationTime = CredentialsMock.CURRENT_TIME_MS + 123456L * 1000;
+        when(storage.retrieveLong("com.auth0.credentials_expires_at")).thenReturn(expirationTime);
+        when(storage.retrieveBoolean("com.auth0.credentials_can_refresh")).thenReturn(false);
+        when(storage.retrieveString("com.auth0.credentials")).thenReturn("{\\"id_token\\":\\"idToken\\"}");
+        when(storage.retrieveString("com.auth0.manager_key_alias")).thenReturn("old_alias");
+        assertThat(manager.hasValidCredentials(), is(false));
+
+        when(storage.retrieveString("com.auth0.credentials")).thenReturn("{\\"access_token\\":\\"accessToken\\"}");
+        assertThat(manager.hasValidCredentials(), is(false));
+    }
+
+    @Test
+    public void shouldNotHaveCredentialsWhenTheAliasUsedHasNotBeenSetYet() {
+        long expirationTime = CredentialsMock.CURRENT_TIME_MS + 123456L * 1000;
+        when(storage.retrieveLong("com.auth0.credentials_expires_at")).thenReturn(expirationTime);
+        when(storage.retrieveBoolean("com.auth0.credentials_can_refresh")).thenReturn(false);
+        when(storage.retrieveString("com.auth0.credentials")).thenReturn("{\\"id_token\\":\\"idToken\\"}");
+        when(storage.retrieveString("com.auth0.manager_key_alias")).thenReturn(null);
+        assertThat(manager.hasValidCredentials(), is(false));
+
+        when(storage.retrieveString("com.auth0.credentials")).thenReturn("{\\"access_token\\":\\"accessToken\\"}");
+        assertThat(manager.hasValidCredentials(), is(false));
+    }
+
     /*
      * Authentication tests
      */
@@ -860,6 +895,7 @@ public class SecureCredentialsManagerTest {
         when(storage.retrieveString("com.auth0.credentials")).thenReturn(encoded);
         when(storage.retrieveLong("com.auth0.credentials_expires_at")).thenReturn(willExpireAt != null ? willExpireAt.getTime() : null);
         when(storage.retrieveBoolean("com.auth0.credentials_can_refresh")).thenReturn(hasRefreshToken);
+        when(storage.retrieveString("com.auth0.manager_key_alias")).thenReturn(SecureCredentialsManager.KEY_ALIAS);
         return storedJson;
     }