aws-amplify / aws-sdk-android

AWS SDK for Android. For more information, see our web site:
https://docs.amplify.aws
Other
1.02k stars 549 forks source link

The Cognito session expires after SDK version updated to V2 #3345

Closed bhavik45 closed 12 months ago

bhavik45 commented 1 year ago

Describe the bug I have implemented AWS Cognito login using AWS SDK Android.

It works well in other cases but when I kill the app and relaunch it, The session expires and it goes into the getAuthenticationDetails method after updating the library versions.

It was working fine before updating the AWS SDK and Amplify SDK. The issue arises after updating AWS SDK from 2.35.0 to 2.71.0 and Amplify SDK from 1.36.2 to 2.8.4.

I have two instances of the same project one is staging and one is production. It works well in my Staging instance but not working in production. The version is the same in both cases. Everything at the code level is identical in both the instances so the cognito and Userpool settings. So not sure what the issue can be.

A code sample:

pool.getUser(userId).getSessionInBackground(new AuthenticationHandler() {
            @Override
            public void onSuccess(CognitoUserSession userSession, CognitoDevice newDevice) {
                token = userSession.getAccessToken().getJWTToken();
                semaphore.release();
            }

            @Override
            public void getAuthenticationDetails(AuthenticationContinuation authenticationContinuation, String userId) {
                lastTokenRetrievalFailureMessage = "Cognito Userpools is not signed-in";
                Log.d("Cognito", "getAuthenticationDetails: ");
                semaphore.release();
            }

            @Override
            public void getMFACode(MultiFactorAuthenticationContinuation continuation) {
                lastTokenRetrievalFailureMessage = "Cognito Userpools is not signed-in";
                Log.d("TAG", "getMFACode: ");
                semaphore.release();
            }

            @Override
            public void authenticationChallenge(ChallengeContinuation continuation) {
                lastTokenRetrievalFailureMessage = "Cognito Userpools is not signed-in";
                Log.d("TAG", "authenticationChallenge: ");
                semaphore.release();
            }

            @Override
            public void onFailure(Exception exception) {
                //lastTokenRetrievalFailureMessage = "Cognito Userpools failed to get session";
//                FirebaseCrashlytics.getInstance().recordException(exception);
                semaphore.release();
            }
        });

Which AWS service(s) are affected? aws-auth-cognito

Environment Information (please complete the following information):

tylerjroach commented 1 year ago

@bhavik45 What use case are you attempting to solve by using both the AWS Android SDK and Amplify v2 at the same time?

These 2 libraries are not compatible with each other. Amplify v1 uses the AWS Android SDK under the hood, but in Amplify v2, we are using the newer Kotlin SDK. Amplify v2 runs a data migration on AWS Android SDK data, rendering the SDK in an unusable state.

If Amplify Android v2 and the Kotlin SDK escape hatch does not fit a use case you are trying to solve, please follow up.

bhavik45 commented 1 year ago

@tylerjroach I have used AWS SDK for login and managing sessions. And I used Amplify methods for storage. Now I've updated both library versions but after updating Amplify library(aws-auth-cognito) session is not being maintained properly.

I don't know why it's affecting sessions as I haven't used Amplify methods for that.

tylerjroach commented 1 year ago

Amplify will migrate the preferences files of the SDK to a new format (and clear the old). The two libraries are not compatible. You should be able to use Amplify Auth for authentication (https://docs.amplify.aws/lib/auth/getting-started/q/platform/android/) instead of AWS SDK.

bhavik45 commented 1 year ago

@tylerjroach We have to maintain the session of all the logged-in users. Users who have already logged in to the app can log in again on the same device in offline mode too. So we are keeping all the logged-in users’ sessions and fetching the session using the userId from the array.

Also, If the currently logged-in user logs in to another device, we need to log that user out from the previous device.

Can we achieve this using Amplify SDK?

tylerjroach commented 1 year ago

Can you point me to how you were handling multiple sessions at once using the AWS Android SDK?

bhavik45 commented 1 year ago

Maybe my previous explanation misleads you. I Apologise for that.

Here's how I'm currently using AWS SDK for authentication.

First of all, for Sign in, I'm using this method -

initiateUserAuthentication(
    AuthenticationDetails(mobileNo, password, null), object : AuthenticationHandler {
                override fun onSuccess(
                    userSession: CognitoUserSession?,
                    newDevice: CognitoDevice?
                ) {
                    cont.resume(userSession)
                }

                override fun getAuthenticationDetails(
                    authenticationContinuation: AuthenticationContinuation?,
                    userId: String?
                ) {
                    val authenticationDetails = AuthenticationDetails(mobileNo, password, null)
                    authenticationContinuation?.setAuthenticationDetails(authenticationDetails)
                    authenticationContinuation?.continueTask()
                }

                override fun getMFACode(continuation: MultiFactorAuthenticationContinuation?) {
                }

                override fun authenticationChallenge(continuation: ChallengeContinuation?) {
                }

                override fun onFailure(exception: Exception) {
                    cont.resumeWithException(exception)
                }
            },
            false
        ).run()

Not if the user is logging out, we are not calling the logout function. We are just removing the user's login data from preference and redirecting the user to the login screen. So this way the user will still be there in the user pool.

Now, let's say 5 users have logged in to the app, the userPool will have active sessions of 5 users.

We have another login option, which is to log in with a device pin. If the user is already logged in to the application using his password and the user had set the device pin(internal feature), next time that user can log in with the user name and device pin. So in the current case, 5 users that are available in userPool will be able to log in with a device pin.

So, any user can log in to the app using the device pin even if the device is offline and can do offline operations in the application.

Note - Here, we are not allowing users to make any call like changing passwords, Cognito profile updates, etc without the internet connection. For that internet connection is required. we are just allowing the users to access the offline available features.

Now, let's say one user among those 5 users is logged in using his device pin, at that time we will not call the Cognito sign-in method. We will just authenticate the user with the locally available profile along with his device pin verification.

Now, if that user is online and try to update his password, in that case, we need to send him the verification code to his registered phone number. For that, first we are fetching that user from the userPool and then calling the change password function for that user.

Here's how we are doing it -

userPool.getUser(phoneNumber).changePassword(oldPassword, newPassword)
suspend fun CognitoUser.changePassword(oldPassword: String, newPassword: String) = suspendCoroutine<Unit> { cont ->

    changePasswordInBackground(oldPassword, newPassword, object : GenericHandler {
        override fun onSuccess() {
            cont.resume(Unit)
        }

        override fun onFailure(exception: Exception) {
            cont.resumeWithException(exception)
        }
    })

}

This is how we are making all the AWS calls by fetching users from the userPool using 'userPool.getUser(PhoneNumber)'.

tylerjroach commented 1 year ago

I'm still confused in a few areas, but lets break this down into small pieces.

Not if the user is logging out, we are not calling the logout function. We are just removing the user's login data from preference and redirecting the user to the login screen. So this way the user will still be there in the user pool.

Which "preference" is being removed. Is this your own SharedPreferences flag, or are you referring to the shared preferences file that the SDK creates.

Now, let's say 5 users have logged in to the app, the userPool will have active sessions of 5 users.

I want to quickly clarify here, that you are referring to 5 users logged in, but on different devices and not 5 users on 1 device.

Now, if that user is online and try to update his password, in that case, we need to send him the verification code to his registered phone number. For that, first we are fetching that user from the userPool and then calling the change password function for that user.

If you never actually called the AWS signOut method (and were instead clearing your own prefs you were setting, such as "isSignedIn = true"), then you should still be able to call Amplify Auth APIs.

bhavik45 commented 1 year ago

@tylerjroach Here are my answer to your questions.

Not if the user is logging out, we are not calling the logout function. We are just removing the user's login data from preference and redirecting the user to the login screen. So this way the user will still be there in the user pool.

Which "preference" is being removed. Is this your own SharedPreferences flag, or are you referring to the shared preferences file that the SDK creates.

I have used the local sharedPreference flag. Not the one that SDK creates

Now, let's say 5 users have logged in to the app, the userPool will have active sessions of 5 users.

I want to quickly clarify here, that you are referring to 5 users logged in, but on different devices and not 5 users on 1 device.

5 users login to 1 device.

When a user logs in, his session will be there in the userPool. Then, when he logs out from the application, we are just clearing the local sharedPreference flag. We are not calling the AWS logout method. This way that user’s session will be there in the userPool.

Now if another user logs in to the same device, there will be active sessions for both the users in the user pool.

So as per my example in my previous comment, if 5 users logs in to the same device, there will be active sessions for all 5 users in the userpool.

We just need to fetch particular user’s session using the below method

userPool.getUser(phoneNumber)

But in Amplify library you can directly call - Amplify.Auth

which will use the currently logged-in user’s session. We want to call the method for a particular user with his/her userId/phoneNumber.

Now, if that user is online and try to update his password, in that case, we need to send him the verification code to his registered phone number. For that, first we are fetching that user from the userPool and then calling the change password function for that user.

If you never actually called the AWS signOut method (and were instead clearing your own prefs you were setting, such as "isSignedIn = true"), then you should still be able to call Amplify Auth APIs.

If I call Amplify.Auth APIs, it’ll use the last logged-in user’s session(Here I’m referring to login with username and password in Cognito). But I want to access those methods for any of the users that are available in userPool using their userId/phoneNumber.

tylerjroach commented 1 year ago

Neither Amplify v1 or v2 support multiple logins on the device at once. From your use case, it appears that you were able to use the AWS for Android mobile sdk directly, to support the multiple logins on a single device use case.

My recommendation would be to stick with Amplify v1 if you must continue to use the AWS Android SDK. Amplify v2 and the AWS Android SDK are not compatible with each other.

Under the hood, Amplify v2 uses the AWS Kotlin SDK (https://github.com/awslabs/aws-sdk-kotlin). Here you may be able to use the Amplify Auth escape hatch to handle multiple users per device. However, the AWS Kotlin SDK will not manage the storing of credentials for you. The Kotlin SDK provides direct access to Cognito API calls but it would be your responsibility to handle those responses and make further decisions (storing tokens on device, managing when to refresh, etc).

mattcreaser commented 12 months ago

Closing due to inactivity. Please open another issue if you need anything else.