Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Getting "Invalid Refresh Token" during session while device tracking is on #2506

Closed
1 task done
Gesraha101 opened this issue Jul 6, 2023 · 18 comments · Fixed by #2614
Closed
1 task done

Getting "Invalid Refresh Token" during session while device tracking is on #2506

Gesraha101 opened this issue Jul 6, 2023 · 18 comments · Fixed by #2614
Assignees
Labels
auth Related to the Auth category/plugins

Comments

@Gesraha101
Copy link

Gesraha101 commented Jul 6, 2023

Before opening, please confirm:

Language and Async Model

Kotlin - Coroutines

Amplify Categories

Authentication, REST API

Gradle script dependencies

// Put output below this line
implementation(com.amplifyframework:aws-api:2.8.7)
implementation(com.amplifyframework:aws-auth-cognito:2.8.7)
implementation(com.amplifyframework:core-kotlin:2.8.7)
coreLibraryDesugaring(com.android.tools:desugar_jdk_libs:2.0.3)

Environment information

# Put output below this line

------------------------------------------------------------
Gradle 7.5
------------------------------------------------------------

Build time:   2022-07-14 12:48:15 UTC
Revision:     c7db7b958189ad2b0c1472b6fe663e6d654a5103

Kotlin:       1.6.21
Groovy:       3.0.10
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          18 (Oracle Corporation 18+36-2087)
OS:           Windows 11 10.0 amd64

Please include any relevant guides or documentation you're referencing

https://repost.aws/knowledge-center/cognito-invalid-refresh-token-error

Describe the bug

The issue I am having is happening only with live daily users. I have tried reproducing it locally on test devices but couldn't succeed. I am using v2.8.5 currently (upgraded from 1.33.2). I encountered the issue in the old version but ignored it as a small percentage were blocked by it but after the upgrade, that percentage increased heavily.

In short, users are getting the SessionExpiredException{message=Your session has expired., cause=NotAuthorizedException(message=Invalid Refresh Token.), recoverySuggestion=Please sign in and reattempt the operation.} (on v2.8.5) and SessionExpiredException{message=Your session has expired., cause=null, recoverySuggestion=Please sign in and reattempt the operation.} (on v1.33.2) errors during their sessions when calling fetchAuthSession.

Reports couldn't determine at what point they encounter it, but I am assuming it's when the ID or access tokens need refreshing. I have tried reproducing the issue by logging in using the same live build and waiting for ID and access tokens to expire then launch the app once again, but tokens got refreshed normally. I also tried logging out after logging in then trying to call fetchAuthSession but that produced a different error SignedOutException{message=You are currently signed out., cause=null, recoverySuggestion=Please sign in and reattempt the operation.}.

All similar issues I found suggested I turn off device tracking to avoid getting that error, but it is crucial to one of the app's functionalities. Some suggested I "send device key with AuthParameters" but I checked the SDK's internal implementation and it's already doing so.

Then I found this and double checked every point mentioned there:

  • I am only using one clientId in configurations
  • I am using device tracking but as far as I learned, the SDK handles sending device key with InitiateAuth call and confirm the device with ConfirmDevice call
  • I am using USER_SRP_AUTH flow with device tracking

Here is a snippet of my initialization and configuration:

AWSApiPlugin.builder().configureClient(API_UNAUTHORIZED).build()
val config = AmplifyConfiguration.builder(applicationContext, R.raw.amplifyconfiguration)
                      .devMenuEnabled(false)
                      .build()
        with(Amplify) {
            addPlugin(AWSCognitoAuthPlugin())
            addPlugin(awsApiPlugin)`
            addPlugin(AWSS3StoragePlugin())
            configure(config, applicationContext)
        }

And here is the sign in code:

Amplify.Auth.signIn(username = username, password = password)

And the fetchAuthSession code:

val session = Amplify.Auth.fetchAuthSession() as? AWSCognitoAuthSession
                val accessToken = session?.userPoolTokensResult?.value?.accessToken
                if (session?.userPoolTokensResult?.error == null) {
                    val userAttributes = Amplify.Auth.fetchUserAttributes()
                    var uuid: String? = null
                    var username: String? = null
                    var phone: String? = null
                    userAttributes.forEach {
                        if (it.key.keyString == CognitoConstants.USER_UUID) {
                            uuid = it.value
                        }
                        if (it.key.keyString == CognitoConstants.CUSTOM_DISPLAY_NAME) {
                            `username = it.value
                        }`
                        if (it.key.keyString == CognitoConstants.CUSTOM_PHONE_NUMBER) {
                           phone = it.value
                        }
                    }
                    return UserSessionInfoResponse(
                        uuid,
                        username,
                        phone,
                        accessToken
                    )
                } else throw Exception(session.userPoolTokensResult.error)

Am I missing something?

Reproduction steps (if applicable)

No response

Code Snippet

// Put your code below this line.

Configs ========================================== 
        val awsApiPlugin = AWSApiPlugin.builder().configureClient(API_UNAUTHORIZED).build()

        val config =
            AmplifyConfiguration.builder(applicationContext, R.raw.amplifyconfiguration)
                .devMenuEnabled(false)
                .build()

        with(Amplify)
        {
            addPlugin(AWSCognitoAuthPlugin())
            addPlugin(awsApiPlugin)
            addPlugin(AWSS3StoragePlugin())
            configure(config, applicationContext)
        }

Sign in ========================================== 
Amplify.Auth.signIn(username = username, password = password)

FetchAuthSession ========================================== 
                val session = Amplify.Auth.fetchAuthSession() as? AWSCognitoAuthSession
                val accessToken = session?.userPoolTokensResult?.value?.accessToken
                if (session?.userPoolTokensResult?.error == null) {
                    val userAttributes = Amplify.Auth.fetchUserAttributes()
                    var uuid: String? = null
                    var username: String? = null
                    var phone: String? = null
                    userAttributes.forEach {
                        if (it.key.keyString == CognitoConstants.USER_UUID) {
                            uuid = it.value
                        }
                        if (it.key.keyString == CognitoConstants.CUSTOM_DISPLAY_NAME) {
                            username = it.value
                        }
                        if (it.key.keyString == CognitoConstants.CUSTOM_PHONE_NUMBER) {
                            phone = it.value
                        }
                    }
                    return UserSessionInfoResponse(
                        uuid,
                        username,
                        phone,
                        accessToken
                    )
                } else throw Exception(session.userPoolTokensResult.error)

Log output

// Put your logs below this line
SessionExpiredException{message=Your session has expired., cause=NotAuthorizedException(message=Invalid Refresh Token.), recoverySuggestion=Please sign in and reattempt the operation.} on v2.8.5
AND
SessionExpiredException{message=Your session has expired., cause=null, recoverySuggestion=Please sign in and reattempt the operation.} on v1.33.2

amplifyconfiguration.json

{
    "UserAgent": "aws-amplify-cli/2.0",
    "Version": "1.0",
    "api": {
        "plugins": {
            "awsAPIPlugin": {
                "fantoken": {
                    "endpointType": "REST",
                    "endpoint": "https://********.com",
                    "region": "us-east-1",
                    "authorizationType": "NONE"
                }
            }
        }
    },
    "auth": {
        "plugins": {
            "awsCognitoAuthPlugin": {
                "UserAgent": "aws-amplify/cli",
                "Version": "0.1.0",
                "IdentityManager": {
                    "Default": {}
                },
                "CredentialsProvider": {
                    "CognitoIdentity": {
                        "Default": {
                            "PoolId": "us-east-1:xxxxx-xxxxxx-xxxxxxxx",
                            "Region": "us-east-1"
                        }
                    }
                },
                "CognitoUserPool": {
                    "Default": {
                        "PoolId": "us-east-1_xxxxxx",
                        "AppClientId": "xxxxxxxxxxxxxxxxxx",
                        "Region": "us-east-1"
                    }
                },
                "Auth": {
                    "Default": {
                        "authenticationFlowType": "USER_SRP_AUTH",
                        "socialProviders": [],
                        "usernameAttributes": [
                            "PHONE_NUMBER"
                        ],
                        "passwordProtectionSettings": {
                            "passwordPolicyMinLength": 8,
                            "passwordPolicyCharacters": [
                                "REQUIRES_NUMBERS",
                                "REQUIRES_SYMBOLS"
                            ]
                        },
                        "mfaConfiguration": "OFF",
                        "mfaTypes": [
                            "SMS"
                        ],
                        "verificationMechanisms": [
                            "PHONE_NUMBER"
                        ]
                    }
                }
            }
        }
    },
    "apiKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

GraphQL Schema

// Put your schema below this line

Additional information and screenshots

No response

@sameera26
Copy link

I also get exact same error. I can simulate this error on my device. I noticed that SDK(2.8.7) renews the token only once when device tracking is enabled. Second time when I try to refresh the token it always throws above exception as mentioned. Here is my implementation

val options = AuthFetchSessionOptions.builder().forceRefresh(true).build()
                Amplify.Auth.fetchAuthSession(options,
                    {
                        val cognitoAuthSession: AWSCognitoAuthSession = it as AWSCognitoAuthSession
                        if (cognitoAuthSession.isSignedIn) {
                            LogUtil.printDebug(TAG, "user is already signed in")
                            when (cognitoAuthSession.userPoolTokensResult.type) {
                                AuthSessionResult.Type.SUCCESS -> {
                                    val cognitoTokenObject = cognitoAuthSession.userPoolTokensResult
                                    cognitoTokenObject.value.let { cognitoToken ->

                                        LogUtil.printDebug(
                                            TAG,
                                            "accessToken = ${cognitoToken?.accessToken}"
                                        )

                                        LogUtil.printDebug(
                                            TAG,
                                            "refreshToken = ${cognitoTokenObject.value?.refreshToken}"
                                        )
                                    }
                                    setAutoLoginComplete()
                                }
                                AuthSessionResult.Type.FAILURE -> {
                                    LogUtil.printError(TAG, "Failed to fetch auth session")
                                    setAutoLoginComplete()
                                    // show login screen
                                }
                            }
                        } else {
                            LogUtil.printDebug(TAG, "user is not signed in")
                        }
                    }, {
                        LogUtil.printError(TAG, "Failed to fetch auth session")
                    })

Steps to Reproduce

  1. Enable device tracking
  2. Authenticate with app
  3. Wait 15min (as I have set my token validity to 15min and refresh token validity is 30 days)
  4. Relaunch application to refresh token

first time SDK does the token renewal correctly. Second time when I retry the above steps, it throws Invalid Refresh Token exception. If I disable device tracking no issue. Hope this is helpful to troubleshoot the issue.

@Gesraha101
Copy link
Author

I tried reproducing the error as in @sameera26's case on my end locally but tokens were refreshed normally. I also tried the live build the users are using but same thing happened. The only difference with my code and @sameera26's is that mine doesn't use forceRefresh as in original snippet in question

@gpanshu gpanshu added the auth Related to the Auth category/plugins label Jul 12, 2023
@sameera26
Copy link

@gpanshu Friendly follow up on above issue. May I know any solution for this issue. Currently we can't disable device tacking as it will affect iOS login flow. Refresh token is a critical feature for our app as we are developing streaming app we can't ask users to login every time when token expired. Please let me know if need additional information on this issue to troubleshoot. Thanks!

@gpanshu
Copy link
Contributor

gpanshu commented Jul 18, 2023

Hi @sameera26 can you add Amplify.addPlugin(AndroidLoggingPlugin(LogLevel.VERBOSE)) on your local build as the first plugin in your application class and post the debug logs here from end to end (from first and then consecutive sign ins). Feel free to attach the log file or use paste bin if it is too large. This will help me understand your state machine transitions and it will showcase what your app is actually doing.

@sameera26
Copy link

Hi @gpanshu Thanks for you response. I have attached my logs files for both success and failure attempts.
refreshToken_failed_attempt.txt
refreshToken_success_attempt.txt

@gpanshu
Copy link
Contributor

gpanshu commented Jul 18, 2023

@sameera26 I see deviceMetadata=com.amplifyframework.statemachine.codegen.data.DeviceMetadata$Empty@85d85a8 which means that your device information is not being stored which could be potentially a bug. I am going to investigate this and get back to you. Thank you so much for your patience in this.

@Gesraha101
Copy link
Author

Gesraha101 commented Jul 19, 2023

hello @sameera26 @gpanshu,

We have been investigating the flow and were finally able to turn off device tracking after releasing some updates to handle not having it. Once we did, there have never been a single "invalid refresh token" error report. I do not have other solutions since I can not reproduce it so I appreciate any support.

I also found another suspicious behavior in a different authentication flow. We have a feature in app that allows user to delete his account. I noticed when I delete mine then stay idle for the token duration (5 mins in my case) that cognito response from Auth.fetchAuthSession stays the same (isSignedIn is still true and all tokens returned normally even with forceRefresh option enabled). When sending that token to our backend, it responds that token has expired.

I have also noticed that in the above flow, the Auth.updateUserAttributes and Auth.fetchUserAttributes both returning "access token expired" error.

I have logged the flow steps in a file for retracing with the plugin's verbose option enabled. The 15 mins gap in logs was to await for tokens to expire. Note that the trace does not include a call to Auth.updateUserAttributes or Auth.fetchUserAttributes since we optimized the code to use cached values instead to prevent further errors.
Delete account flow trace.txt

@sameera26
Copy link

@Gesraha101 May I know didn't you face any login issue for the iOS users once you disabled device tracking. Because we are facing an issue for our iOS client when disabling device tracking. They can't login at all. Therefore disabling device tracking to solve Android token refreshing issue also not a workable solution for us.

@gpanshu This issue is a blocker for us to proceed to next app release. Appriciate your kind attention on this issue. Thanks!

@gpanshu
Copy link
Contributor

gpanshu commented Jul 21, 2023

@sameera26 and @Gesraha101 cognito mandates all new devices that logs in to be confirmed using the ConfirmDevice API call otherwise they will not let the refresh token refresh the access token. Having said that the sign in call for flows other than hostedUI should automatically call the confirm device api. I am now digging deep using the examples provided by @Gesraha101 to understand what might be going on here as it might be a bug but I am investigating and will report back here once I am done. Thank you so much for your patience.

@Gesraha101
Copy link
Author

Gesraha101 commented Jul 26, 2023

@sameera26 no we haven't faced any issues after disabling device tracking on iOS AFAIK. We got some complaints from users, however, that implied same issue existed on iOS when device tracking was on but wasn't covered in reports

@sameera26
Copy link

@gpanshu Any update on this?

@Gesraha101
Copy link
Author

@gpanshu just a friendly follow up on this.
I am well aware you mentioned you would update us once you are done but I am required to report back to our management when the device tracking can be used without issues to plan ahead. So please let us know if there is a scheduled date to address that issue.

Thanks in advance.

@sameera26
Copy link

sameera26 commented Sep 14, 2023

@gpanshu We have intercepted refresh token API through Charles proxy and compared the request payload of success and failure scenario. We noticed that Android SDK doesn't send the DEVICE_KEY when calling refresh token API and it's returning 400 error. Herewith I have attached my payloads for both success and failure scenarios. Could you help to further investigate and advise us how to resolve this issue.

payload_when_fail:
payload_whenfail

payload_when_success
payload_whensuccess

Note: We tried to intercept iOS client's cognito API calls through proxy settings but we couldn't detect the API calls for iOS

CC: @tylerjroach

@tylerjroach
Copy link
Contributor

@sameera26 Thank you for the followup. Will take a look.

@tylerjroach
Copy link
Contributor

While not directly related, there was also a report of device tracking information not being available immediately after sign in: #2506 (comment).

Will look into additional paths where device metadata may be forgotten from the in-memory cache (still available on Shared Preferences), resulting in it not being passed into all refresh calls.

@MarimuthuP
Copy link

MarimuthuP commented Sep 15, 2023

I'm facing a similar issue as @sameera26 mentioned above. We're unable to refresh the access token, which forces users to log out when encountering this exception since the access token is valid for 24 hours, so users have to log in every day. Kindly advise how to resolve this issue or If you have any workarounds, please share. Thanks!

CC: @tylerjroach

@dhanmoti
Copy link

dhanmoti commented Sep 21, 2023

My team is also facing same issue. @tylerjroach Any update on this issue?

@github-actions
Copy link
Contributor

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auth Related to the Auth category/plugins
Projects
None yet
6 participants