aws-amplify / amplify-android

The fastest and easiest way to use AWS from your Android app.
https://docs.amplify.aws/lib/q/platform/android/
Apache License 2.0
247 stars 117 forks source link

Cannot signIn after signIn with social provider + logout #873

Closed Sserra90 closed 3 years ago

Sserra90 commented 4 years ago

Hey guys,

After an initial social sign-in, if I logout, I'm facing an issue where I can't sign back in, again.

Steps to reproduce

It does not have to be the same account for both logins.

Sign-in with username/password code

amplifyProxy.signIn(
    credentials,
    Consumer {
        // Here AuthSignInResult returns with isSignInComplete = true
        runBlocking {
            try {
                val session = fetchAuthSession()
                // After fetching the session i get the message => Please sign in and reattempt the operation
                cont.resume(result)
            } catch (e: Exception) {
                Logger.logException(e)
                cont.resume(Result.Error(e))
            }
        }
    }
    ...
    // code snippet cuts off here

An error is returned after calling fetchAuthSession:

AWSCognitoAuthSession{isSignedIn=true, awsCredentials=AuthSessionResult{value=null, error=AmplifyException {message=Your session has expired., cause=null, recoverySuggestion=Please sign in and reattempt the operation.}, type=FAILURE}, userSub='AuthSessionResult{value=null, error=AmplifyException {message=Your session has expired., cause=null, recoverySuggestion=Please sign in and reattempt the operation.}, type=FAILURE}', identityId='AuthSessionResult{value=null, error=AmplifyException {message=Your session has expired., cause=null, recoverySuggestion=Please sign in and reattempt the operation.}, type=FAILURE}', userPoolTokens='AuthSessionResult{value=null, error=AmplifyException {message=Your session has expired., cause=null, recoverySuggestion=Please sign in and reattempt the operation.}, type=FAILURE}'}

Sign-in returns success, but immediately after that, when fetching the auth session, it returns an error:

Fetch auth session code

private suspend fun fetchAuthSession(): AuthSession {
    return suspendCancellableCoroutine { cont ->
        amplifyProxy.fetchAuthSession(
            Consumer { result -> cont.resume(result) },
            Consumer { error -> cont.resumeWithException(error) }
        )
    }
}

After debugging, I can see the problem is raised in _getToken() -> userpool.getCurrentUser().getSession() -> getCachedSession() where userId == null:

protected CognitoUserSession getCachedSession() {
    synchronized (GET_CACHED_SESSION_LOCK) {
        if (userId == null) {
            throw new CognitoNotAuthorizedException("User-ID is null");
        }
    // code snippet ends

After that i receive an exception saying:

No cached session

UserState userState = UserState.SIGNED_IN;
if (isSignedOutRelatedException(userPoolsException)) {
    userState = UserState.SIGNED_OUT_USER_POOLS_TOKENS_INVALID;
}
final UserStateDetails userStateDetails = new UserStateDetails(userState, details);
userStateDetails.setException(userPoolsException);
return userStateDetails;

If I clear app cache, then I'm able to sign-in again using a username/password. Otherwise, it gets stuck forever. It seems to be an inconsistency with local cache after sign-in + logout with social provider.

Amplify version 1.3.2 Authentication flow type is set to "USER_PASSWORD_AUTH"

jamesonwilliams commented 4 years ago

Hi @Sserra90, I tried to clean up your issue a bit ... but I'm not sure I get it still. Can you help out a bit?

  1. What's amplifyProxy?
  2. Can you please close all blocks in your code snippets, to show where they logically are meant to end?
  3. In the second part of your question, you include some Java code. Can you clarify what this is? Is this code that you're referencing from the AWSMobileClient? If so, can you include line links, in addition to the inline Java blocks?
Sserra90 commented 4 years ago

Hi @jamesonwilliams thanks for the reply. Let me try to clarify this a little.

  1. amplifyProxy just proxy calls to real Amplify instance, it's here just to ease unit testing, so we can remove it from the example.

After some more research i can always reproduce this problem doing the following steps:

  1. Sign in with a social provider. In my case i used Google.
  2. Logout.
  3. Sign in with a email/password.
  4. Logout
  5. Try to sign in again with a email/password, it does not work anymore. I need to clear app cache to be able to sign in back again.

I used the same email for both social and username/password signin but it's not required to be the same. I had other developer reproducing this with different emails.

PS: I can signin + logout multiple times with a email/password without any problems. It happens when we add social signin to the equation.

Below is the code used to reproduce the issue


// Launch web UI for social signin
fun signIn(activity: Activity, provider: AuthProvider): LiveData<Result<Boolean>> {

        val lv = MutableLiveData<Result<Boolean>>()

        // Launch cognito web social UI.
        awsAuth.signInWithSocialWebUI(
                provider,
                activity,
                { result ->

                    if (result.isSignInComplete) {
                        lv.postValue(Result.Success(true))
                    }

                },
                { error ->

                    val userCancelled = error.cause?.message == "user cancelled"
                    val errorCode = if (userCancelled) ErrorCode.USER_CANCELLED else ErrorCode.UNKNOWN
                    lv.postValue(Result.Error(AuthenticationException(errorCode)))
                }
        )

        return lv
    }

    private suspend fun fetchAuthSession(): AuthSession {
        return suspendCancellableCoroutine { cont ->
            amplifyProxy.fetchAuthSession(
                    Consumer { result -> cont.resume(result) },
                    Consumer { error -> cont.resumeWithException(error) }
            )
        }
    }

    // Code used to signin a user with email/password strategy.
    override suspend fun signInWithCredentials(credentials: Credentials): Result<Boolean> {
        return suspendCancellableCoroutine { cont ->
            awsAuth.signIn(
                    credentials.email.value,
                    credentials.password.value,
                    {

                        runBlocking {

                            try {

                                val session = fetchAuthSession()
                                val result = if (session.isSignedIn) {

                                    // Here at step number 5, isSignedIn is true but we receive an error saying => "Your session has expired"
                                    // and no tokens are available.
                                    Result.Success(true)
                                } else Result.Error(RuntimeException("User not signedIn"))
                                cont.resume(result)

                            } catch (e: Exception) {
                                logError(e, credentials.email.value)
                                cont.resume(Result.Error(e))
                            }
                        }

                    },
                    {
                        Logger.logException(it)
                        cont.resume(Result.Error(it))
                    }
            )
        }
    }

    override suspend fun logout(): Result<Boolean> {
        return suspendCancellableCoroutine { cont ->
            awsAuth.signOut(
                    {
                        cont.resume(Result.Success(true))
                    },
                    { error ->
                        Logger.logException(error)
                        cont.resume(Result.Error(error))
                    }
            )
        }
    }

The code below is just for initial guidance, to help trace the problem, if it does not help please ignore it.

Tracing the calls, fetchAuthSession goes to AwsMobileClient ->

AwsMobileClient. getUserStateDetails(final boolean offlineCheck) calls _getTokens() https://github.com/aws-amplify/aws-sdk-android/blob/a7d6794c965e56d9a019f26261c94a9ff1f19231/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/AWSMobileClient.java#L1042

_getTokens(..) method https://github.com/aws-amplify/aws-sdk-android/blob/a7d6794c965e56d9a019f26261c94a9ff1f19231/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/AWSMobileClient.java#L1788

After trying to get tokens AwsMobileClient. getUserStateDetails enters line 1069 with a an exception that says No cached session https://github.com/aws-amplify/aws-sdk-android/blob/a7d6794c965e56d9a019f26261c94a9ff1f19231/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/AWSMobileClient.java#L1069

You can see _getTokens(...) calling userpool.getCurrentUser().getSession(..) and it ends up at getCachedSession() throwing CognitoNotAuthorizedException("User-ID is null").

https://github.com/aws-amplify/aws-sdk-android/blob/a7d6794c965e56d9a019f26261c94a9ff1f19231/aws-android-sdk-cognitoidentityprovider/src/main/java/com/amazonaws/mobileconnectors/cognitoidentityprovider/CognitoUser.java#L1279

Hope this helps to clarify the problem.

falary1 commented 3 years ago

Hi @Sserra90

I had exactly the same problem. I managed to get out using the globalSignOut(true) option on my signout.

https://docs.amplify.aws/lib/auth/signOut/q/platform/android

Hope this helps

Sserra90 commented 3 years ago

@falary1 Thanks, I'll try to reproduce the issue again and see it that fixes it. However for me it's not an option i don't want a global signout every time a user logs out.

Sserra90 commented 3 years ago

Hi all, after the last comment i spent some time trying to debug this issue in depth and i believe i found what the problem is.

I think this issue is also suffering from the same problem https://github.com/aws-amplify/aws-sdk-android/issues/1469

Like i said in previous comment you can reproduce this using the following steps:

  1. Sign in with a social provider. In my case i used Google.
  2. Logout.
  3. Sign in with a email/password.
  4. Logout
  5. Try to sign in again with a email/password, it does not work anymore. I need to clear app cache to be able to sign in back again.

There must be a social signIn in the mix to make this happen. By monitoring the changes to shared_prefs/CognitoIdentityProviderCache.xml during every step above this is whats happening.

1- After the user signIn using hostedUI the contents of the file are updated with idToken, refreshToken, accessToken and LastAuthUser

2 - Users signs out, then the contents of the file are cleared. So far so good.

3 - Users signIn with username and password. Again the contents of the file are updated with idToken, refreshToken, accessToken and LastAuthUser

4 - Users signs out. The contents of file are not cleared! only the LastAuthUser entry is deleted. After this the user can no longer sign in until he clears app cache to delete the CognitoIdentityProviderCache.xml, if he tries the following exception is raised:

W/AWSMobileClient: Tokens are invalid, please sign-in again.
java.lang.Exception: No cached session.
    at com.amazonaws.mobile.client.AWSMobileClient$12.onFailure(AWSMobileClient.java:1818)
    at com.amazonaws.mobileconnectors.cognitoauth.AuthClient.getSession(AuthClient.java:182)
    at com.amazonaws.mobileconnectors.cognitoauth.Auth.getSession(Auth.java:674)
    at com.amazonaws.mobile.client.AWSMobileClient._getHostedUITokens(AWSMobileClient.java:1821)
    at com.amazonaws.mobile.client.AWSMobileClient.access$800(AWSMobileClient.java:161)
    at com.amazonaws.mobile.client.AWSMobileClient$11.run(AWSMobileClient.java:1743)
    at com.amazonaws.mobile.client.internal.InternalCallback.await(InternalCallback.java:115)
    at com.amazonaws.mobile.client.AWSMobileClient.getTokens(AWSMobileClient.java:1717)
    at com.amazonaws.mobile.client.AWSMobileClient.getUserStateDetails(AWSMobileClient.java:1024)
    at com.amazonaws.mobile.client.AWSMobileClient.waitForSignIn(AWSMobileClient.java:903)
    at com.amazonaws.mobile.client.AWSMobileClient$11.run(AWSMobileClient.java:1733)
    at com.amazonaws.mobile.client.internal.InternalCallback.await(InternalCallback.java:115)
    at com.amazonaws.mobile.client.AWSMobileClient.getTokens(AWSMobileClient.java:1699)
    at com.example.tobi.androidapp.LoginActivity$3$1.run(LoginActivity.java:120)
    at android.os.Handler.handleCallback(Handler.java:790)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6494)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

Why is this happening?

Looking at the code i think this is problem:

In AwsMobileClient._signOut(...) method https://github.com/aws-amplify/aws-sdk-android/blob/6cf536fbbc0ec062b94ec62143482848b83d25e1/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/AWSMobileClient.java#L1548 it checks if the hostedUI != null and if it is calls hostedUI.signOut(), because we did a sign in with hostedUI on the first step the code enters here. This method clears the LastAuthUser from preferences file but keeps everything else.

After that

AwsMobileClient.signOut() https://github.com/aws-amplify/aws-sdk-android/blob/6cf536fbbc0ec062b94ec62143482848b83d25e1/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/AWSMobileClient.java#L1586

is called, and calls userPool.getCurrentUser().signOut()

https://github.com/aws-amplify/aws-sdk-android/blob/6cf536fbbc0ec062b94ec62143482848b83d25e1/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/AWSMobileClient.java#L1471

however since LastAuthUser was deleted before now the getCurrentUser() returns a null user and the contents of the file are not cleared, we are now in an inconsistent state. After this the user cannot signIn anymore.

Currently the fix that is working for me is to manually clear the contents of CognitoIdentityProviderCache.xml in the Amplify signout method onSuccess callback. Ex:

amplify.signOut(
        Action {

            /**
             * We need to manually clear local Amplify cache.
             * See https://github.com/aws-amplify/amplify-android/issues/873
             * for more information.
             */
            AWSKeyValueStore(context, "CognitoIdentityProviderCache", true).clear()

            cont.resume(Result.Success(true))
        },
        Consumer { error ->
            Logger.logException(error)
            cont.resume(Result.Error(error))
        }
)

@falary1 i believe doing a globalSignOut works because it goes into a different code flow and avoids this issue.

@jamesonwilliams It would be good if someone from Amplify team could reproduce the issue and confirm if this assumptions are correct. This a nasty issue because it makes the user unable to login again until he clears the app data or reinstall.

Amplify version 1.3.2 Authentication flow type is set to "USER_PASSWORD_AUTH"

falary1 commented 3 years ago

@Sserra90 Hi,

Since my last comment, i tried to clear cache manually with no success (i wanted to avoid globalSignOut() too), and i finally achieved to refresh amplify cache by using AWSMobileClient.getInstance().signOut(); and then, AWSMobileClient.getInstance().refresh(); which i guess did the job by updating cache credentials.

I've read your post, and i think you're right about this issue. Your way to fix it is also working (this is exactly what i was looking for), and i will use it because this is more accurate i think.

Weird that not many of us talk about this ;)

Thanks !

fanwgwg commented 3 years ago

Yes I'm seeing the same issue when using Amplify Flutter on Android, the issue is not happening on iOS. Any fixes on this?

This is important for Amplify Flutter because it hasn't implemented support for globalSignout yet, and I do not want to use globalSignout all the time.

Thanks!

fanwgwg commented 3 years ago

Any updates on this issue?

raphkim commented 3 years ago

This issue is now resolved with v1.17.8. Please follow the updated documentation to apply this fix :)

karmaobserver commented 3 years ago

Hello, I have the same issue even when I am using the new version of the library com.amplifyframework:aws-auth-cognito:1.25.0 (or version v1.17.8). I have done everything according to documentation and this issue still persists for me. Can someone confirm that the issue is really fixed?

Thanks