amplify-swift: Intermittent Session Dumping from Keychain on Unexpected Error in Amplify Auth

Describe the bug

We are encountering issues with Amplify’s session management where several users experience their sessions being removed from the keychain following an unexpected error. This results in a ‘signedOut’ state error when attempting to fetch the auth session from Amplify. The problem does not occur consistently but has been observed under certain conditions.

Steps To Reproduce

(Note: Since the issue does not consistently occur, list the conditions or actions that preceded the occurrence if any patterns were noticed.)

1 - Perform operations that require authentication.
2 - Observe the application's behavior and logs for any unexpected errors leading to session dumping.
3 - Attempt to fetch the Cognito token from Amplify using the recommended API method.

Expected behavior

The auth session should be reliably fetched from the keychain without being inadvertently dumped, allowing for uninterrupted user authentication and session management.

Amplify Framework Version

2.26.4

Amplify Categories

Auth

Dependency manager

Swift PM

Swift version

5

CLI version

12.4.0

Xcode version

15.0.1

Relevant log output

<details>
<summary>Log Messages</summary>


Adding plugin: AWSCognitoAuthPlugin.AWSCognitoAuthPlugin)
Configuring
Configuration: Optional(Amplify.AmplifyConfiguration(analytics: nil, api: nil, auth: Optional(Amplify.AuthCategoryConfiguration(plugins: ["awsCognitoAuthPlugin": Amplify.JSONValue.object(["Auth": Amplify.JSONValue.object(["Default": Amplify.JSONValue.object(["authenticationFlowType": Amplify.JSONValue.string("CUSTOM_AUTH_WITHOUT_SRP")])]), "IdentityManager": Amplify.JSONValue.object(["Default": Amplify.JSONValue.array([])]), "CognitoUserPool": Amplify.JSONValue.object(["Default": Amplify.JSONValue.object(["AppClientId": Amplify.JSONValue.string("g15tgsjnc501c72gibm7qpkr0"), "PoolId": Amplify.JSONValue.string("eu-west-1_Q1M8Rca52"), "Region": Amplify.JSONValue.string("eu-west-1")])])])])), dataStore: nil, geo: nil, hub: nil, logging: nil, notifications: nil, predictions: nil, storage: nil))
Could not find Cognito Identity Pool configuration
Auth state change:

{
    "AuthState.notConfigured" =     {
    };
}
Credential Store state change:

{
    "CredentialStoreState.notConfigured" =     {
    };
}
Auth state change:

{
    "AuthState.configuringAuth" =     {
    };
}
Starting execution for Auth.fetchSessionAPI
Starting execution
Check if authstate configured
AWSCognitoAuthPlugin/InitializeAuthConfiguration.swift Starting execution
Credential Store state change:

{
    "CredentialStoreState.migratingLegacyStore" =     {
    };
}
AWSCognitoAuthPlugin/MigrateLegacyCredentialStore.swift Starting execution
AWSCognitoAuthPlugin/MigrateLegacyCredentialStore.swift Sending event CredentialStoreEvent.loadCredentialStore
Credential Store state change:

{
    "CredentialStoreState.loadingStoredCredentials" =     {
    };
}
AWSCognitoAuthPlugin/LoadCredentialStore.swift Starting execution
AWSCognitoAuthPlugin/LoadCredentialStore.swift Retreiving credential amplifyCredentials
AWSCognitoAuthPlugin/LoadCredentialStore.swift Sending event CredentialStoreEvent.throwError
CoreData: debug: PostSaveMaintenance: incremental_vacuum with freelist_count - 18 and pages_to_free 3
No existing session found.
AWSCognitoAuthPlugin/IdleCredentialStore.swift Starting execution
AWSCognitoAuthPlugin/IdleCredentialStore.swift Sending event CredentialStoreEvent.moveToIdleState
AWSCognitoAuthPlugin/InitializeAuthConfiguration.swift Sending event AuthEvent.validateCredentialAndConfiguration
Auth state change:

{
    "AuthState.validatingCredentialsAndConfiguration" =     {
    };
}
Credential Store state change:

{
    "CredentialStoreState.error" =     {
        errorType = "KeychainStoreError: Unable to find the keychain item";
    };
}
Credential Store state change:

{
    "CredentialStoreState.idle" =     {
    };
}
AWSCognitoAuthPlugin/ValidateCredentialsAndConfiguration.swift Starting execution
AWSCognitoAuthPlugin/ValidateCredentialsAndConfiguration.swift Sending event AuthEvent.configureAuthentication
Auth state change:

{
    "AuthState.configuringAuthentication" =     {
        "AuthenticationState.notConfigured" =         {
        };
    };
}
AWSCognitoAuthPlugin/InitializeAuthenticationConfiguration.swift Starting execution
AWSCognitoAuthPlugin/InitializeAuthenticationConfiguration.swift Sending event AuthenticationEvent.configure
Auth state change:

{
    "AuthState.configuringAuthentication" =     {
        "AuthenticationState.configured" =         {
        };
    };
}
AWSCognitoAuthPlugin/ConfigureAuthentication.swift Start execution
AWSCognitoAuthPlugin/ConfigureAuthentication.swift Sending event AuthenticationEvent.initializedSignedOut
Auth state change:

{
    "AuthState.configuringAuthentication" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
    };
}
AWSCognitoAuthPlugin/ConfigureAuthentication.swift Sending event AuthEvent.authenticationConfigured
AWSCognitoAuthPlugin/InitializeAuthorizationConfiguration.swift Starting execution
AWSCognitoAuthPlugin/InitializeAuthorizationConfiguration.swift Sending event AuthorizationEvent.configure
Auth state change:

{
    "AuthState.configuringAuthorization" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
        "AuthorizationState.notConfigured" =         {
        };
    };
}
Auth state change:

{
    "AuthState.configuringAuthorization" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
        "AuthorizationState.configured" =         {
        };
    };
}
AWSCognitoAuthPlugin/ConfigureAuthorization.swift Starting execution
AWSCognitoAuthPlugin/ConfigureAuthorization.swift Sending event AuthEvent.authorizationConfigured
Auth state change:

{
    "AuthState.configured" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
        "AuthorizationState.configured" =         {
        };
    };
}
Auth state configured
Fetching current state
No session found, fetching unauth session
Waiting for session to establish
Auth state change:

{
    "AuthState.configured" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
        "AuthorizationState.fetchingUnAuthSession" =         {
            "FetchSessionState.notStarted" =             {
            };
        };
    };
}
AWSCognitoAuthPlugin/InitializeFetchUnAuthSession.swift Starting execution
AWSCognitoAuthPlugin/InitializeFetchUnAuthSession.swift Sending event FetchAuthSessionEvent.throwError
AWSCognitoAuthPlugin/InformSessionError.swift Starting execution
AWSCognitoAuthPlugin/InformSessionError.swift Sending event AuthorizationEvent.receivedSessionError
Auth state change:

{
    "AuthState.configured" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
        "AuthorizationState.fetchingUnAuthSession" =         {
            "FetchSessionState.error" =             {
                error = "AWSCognitoAuthPlugin.FetchSessionError.noIdentityPool";
            };
        };
    };
}
Received error - sessionError(AWSCognitoAuthPlugin.FetchSessionError.noIdentityPool, noCredentials)
Auth state change:

{
    "AuthState.configured" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
        "AuthorizationState.error" =         {
            Error = "AWSCognitoAuthPlugin.AuthorizationError.sessionError(AWSCognitoAuthPlugin.FetchSessionError.noIdentityPool, noCredentials)";
        };
    };
}
Successfully completed execution for Auth.fetchSessionAPI with result:
{
    awsCredentialsError = "AuthError: Unexpected error occurred with message: Unknown error occurred\nRecovery suggestion: This should not happen. There is a possibility that there is a bug if this error persists. Please take a look at https://github.com/aws-amplify/amplify-swift/issues to see if there are any existing issues that match your scenario, and file an issue with the details of the bug if there isn't.";
    cognitoTokensError = "AuthError: Unexpected error occurred with message: Unknown error occurred\nRecovery suggestion: This should not happen. There is a possibility that there is a bug if this error persists. Please take a look at https://github.com/aws-amplify/amplify-swift/issues to see if there are any existing issues that match your scenario, and file an issue with the details of the bug if there isn't.";
    identityIdError = "AuthError: Unexpected error occurred with message: Unknown error occurred\nRecovery suggestion: This should not happen. There is a possibility that there is a bug if this error persists. Please take a look at https://github.com/aws-amplify/amplify-swift/issues to see if there are any existing issues that match your scenario, and file an issue with the details of the bug if there isn't.";
    isSignedIn = false;
    userSubError = "AuthError: Unexpected error occurred with message: Unknown error occurred\nRecovery suggestion: This should not happen. There is a possibility that there is a bug if this error persists. Please take a look at https://github.com/aws-amplify/amplify-swift/issues to see if there are any existing issues that match your scenario, and file an issue with the details of the bug if there isn't.";
}
Adding plugin: AWSCognitoAuthPlugin.AWSCognitoAuthPlugin)
Starting execution for Auth.fetchSessionAPI
Starting execution
Check if authstate configured
Auth state configured
Fetching current state
Waiting for session to establish
Auth state change:

{
    "AuthState.configured" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
        "AuthorizationState.refreshingSession" =         {
            existing = noCredentials;
            refreshState =             {
                "RefreshSessionState.notStarted" =                 {
                };
            };
        };
    };
}
AWSCognitoAuthPlugin/InitializeRefreshSession.swift Starting execution
AWSCognitoAuthPlugin/InitializeRefreshSession.swift Sending event RefreshSessionEvent.throwError
AWSCognitoAuthPlugin/InformSessionError.swift Starting execution
AWSCognitoAuthPlugin/InformSessionError.swift Sending event AuthorizationEvent.receivedSessionError
Auth state change:

{
    "AuthState.configured" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
        "AuthorizationState.refreshingSession" =         {
            existing = noCredentials;
            refreshState =             {
                "RefreshSessionState.error" =                 {
                    error = "AWSCognitoAuthPlugin.FetchSessionError.noCredentialsToRefresh";
                };
            };
        };
    };
}
Received error - sessionError(AWSCognitoAuthPlugin.FetchSessionError.noCredentialsToRefresh, noCredentials)
Auth state change:

{
    "AuthState.configured" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
        "AuthorizationState.error" =         {
            Error = "AWSCognitoAuthPlugin.AuthorizationError.sessionError(AWSCognitoAuthPlugin.FetchSessionError.noCredentialsToRefresh, noCredentials)";
        };
    };
}
Successfully completed execution for Auth.fetchSessionAPI with result:
{
    awsCredentialsError = "AuthError: There is no user signed in to retreive AWS credentials\nRecovery suggestion: Call Auth.signIn to sign in a user or enable unauthenticated access in AWS Cognito Identity Pool\nCaused by:\ninvalidAccountTypeException";
    cognitoTokensError = "AuthError: There is no user signed in to retreive cognito tokens\nRecovery suggestion: Call Auth.signIn to sign in a user and then call Auth.fetchSession";
    identityIdError = "AuthError: There is no user signed in to retreive identity id\nRecovery suggestion: Call Auth.signIn to sign in a user or enable unauthenticated access in AWS Cognito Identity Pool\nCaused by:\ninvalidAccountTypeException";
    isSignedIn = false;
    userSubError = "AuthError: There is no user signed in to retreive user sub\nRecovery suggestion: Call Auth.signIn to sign in a user and then call Auth.fetchSession";
}
Adding plugin: AWSCognitoAuthPlugin.AWSCognitoAuthPlugin)
Starting execution for Auth.fetchSessionAPI
Starting execution
Check if authstate configured
Auth state configured
Fetching current state
Waiting for session to establish
AWSCognitoAuthPlugin/InitializeRefreshSession.swift Starting execution
Auth state change:

{
    "AuthState.configured" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
        "AuthorizationState.refreshingSession" =         {
            existing = noCredentials;
            refreshState =             {
                "RefreshSessionState.notStarted" =                 {
                };
            };
        };
    };
}
AWSCognitoAuthPlugin/InitializeRefreshSession.swift Sending event RefreshSessionEvent.throwError
AWSCognitoAuthPlugin/InformSessionError.swift Starting execution
AWSCognitoAuthPlugin/InformSessionError.swift Sending event AuthorizationEvent.receivedSessionError
Received error - sessionError(AWSCognitoAuthPlugin.FetchSessionError.noCredentialsToRefresh, noCredentials)
Auth state change:

{
    "AuthState.configured" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
        "AuthorizationState.refreshingSession" =         {
            existing = noCredentials;
            refreshState =             {
                "RefreshSessionState.error" =                 {
                    error = "AWSCognitoAuthPlugin.FetchSessionError.noCredentialsToRefresh";
                };
            };
        };
    };
}
Successfully completed execution for Auth.fetchSessionAPI with result:
{
    awsCredentialsError = "AuthError: There is no user signed in to retreive AWS credentials\nRecovery suggestion: Call Auth.signIn to sign in a user or enable unauthenticated access in AWS Cognito Identity Pool\nCaused by:\ninvalidAccountTypeException";
    cognitoTokensError = "AuthError: There is no user signed in to retreive cognito tokens\nRecovery suggestion: Call Auth.signIn to sign in a user and then call Auth.fetchSession";
    identityIdError = "AuthError: There is no user signed in to retreive identity id\nRecovery suggestion: Call Auth.signIn to sign in a user or enable unauthenticated access in AWS Cognito Identity Pool\nCaused by:\ninvalidAccountTypeException";
    isSignedIn = false;
    userSubError = "AuthError: There is no user signed in to retreive user sub\nRecovery suggestion: Call Auth.signIn to sign in a user and then call Auth.fetchSession";
}
Auth state change:

{
    "AuthState.configured" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
        "AuthorizationState.error" =         {
            Error = "AWSCognitoAuthPlugin.AuthorizationError.sessionError(AWSCognitoAuthPlugin.FetchSessionError.noCredentialsToRefresh, noCredentials)";
        };
    };
}
Adding plugin: AWSCognitoAuthPlugin.AWSCognitoAuthPlugin)
Adding plugin: AWSCognitoAuthPlugin.AWSCognitoAuthPlugin)
Starting execution for Auth.fetchSessionAPI
Starting execution
Check if authstate configured
Auth state configured
Fetching current state
Waiting for session to establish
AWSCognitoAuthPlugin/InitializeRefreshSession.swift Starting execution
AWSCognitoAuthPlugin/InitializeRefreshSession.swift Sending event RefreshSessionEvent.throwError
Auth state change:

{
    "AuthState.configured" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
        "AuthorizationState.refreshingSession" =         {
            existing = noCredentials;
            refreshState =             {
                "RefreshSessionState.notStarted" =                 {
                };
            };
        };
    };
}
AWSCognitoAuthPlugin/InformSessionError.swift Starting execution
AWSCognitoAuthPlugin/InformSessionError.swift Sending event AuthorizationEvent.receivedSessionError
Auth state change:

{
    "AuthState.configured" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
        "AuthorizationState.refreshingSession" =         {
            existing = noCredentials;
            refreshState =             {
                "RefreshSessionState.error" =                 {
                    error = "AWSCognitoAuthPlugin.FetchSessionError.noCredentialsToRefresh";
                };
            };
        };
    };
}
Received error - sessionError(AWSCognitoAuthPlugin.FetchSessionError.noCredentialsToRefresh, noCredentials)
Auth state change:

{
    "AuthState.configured" =     {
        "AuthenticationState.signedOut" =         {
            lastKnownUserName = "(nil)";
        };
        "AuthorizationState.error" =         {
            Error = "AWSCognitoAuthPlugin.AuthorizationError.sessionError(AWSCognitoAuthPlugin.FetchSessionError.noCredentialsToRefresh, noCredentials)";
        };
    };
}
Successfully completed execution for Auth.fetchSessionAPI with result:
{
    awsCredentialsError = "AuthError: There is no user signed in to retreive AWS credentials\nRecovery suggestion: Call Auth.signIn to sign in a user or enable unauthenticated access in AWS Cognito Identity Pool\nCaused by:\ninvalidAccountTypeException";
    cognitoTokensError = "AuthError: There is no user signed in to retreive cognito tokens\nRecovery suggestion: Call Auth.signIn to sign in a user and then call Auth.fetchSession";
    identityIdError = "AuthError: There is no user signed in to retreive identity id\nRecovery suggestion: Call Auth.signIn to sign in a user or enable unauthenticated access in AWS Cognito Identity Pool\nCaused by:\ninvalidAccountTypeException";
    isSignedIn = false;
    userSubError = "AuthError: There is no user signed in to retreive user sub\nRecovery suggestion: Call Auth.signIn to sign in a user and then call Auth.fetchSession";
}
```

Is this a regression?

Yes

Regression additional context

No response

Platforms

iOS

OS Version

iOS 17.1.1

Device

iPhone 17 - Simulator

Specific to simulators

No response

Additional context

At every single request that we make to our BE API we do run the following code to retrieve the Cognito id token:

let session = try await Amplify.Auth.fetchAuthSession()
if let cognitoTokenProvider = session as? AuthCognitoTokensProvider {
    let tokens = try cognitoTokenProvider.getCognitoTokens().get()
    promise(.success((tokens.idToken)))
}

Flow:

  • A request is made.
  • The Cognito token is fetched from Amplify.
  • The token is injected into the Bearer.

In some instances, following an unexpected error, the session gets removed from the keychain, and attempting to fetch the auth session results in a ‘signedOut’ state error.

About this issue

  • Original URL
  • State: open
  • Created 4 months ago
  • Comments: 18 (8 by maintainers)

Most upvoted comments