aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.42k stars 2.12k forks source link

Cannot retrieve a new session. Please authenticate. [Auth + No AppSync] #11156

Closed aliza-khu closed 10 months ago

aliza-khu commented 1 year ago

Before opening, please confirm:

JavaScript Framework

React Native

Amplify APIs

Authentication

Amplify Categories

auth

Environment information

``` # Put output below this line System: OS: macOS 13.1 CPU: (8) arm64 Apple M1 Memory: 1.02 GB / 16.00 GB Shell: 5.8.1 - /bin/zsh Binaries: Node: 19.3.0 - /opt/homebrew/bin/node Yarn: 1.22.19 - /usr/local/bin/yarn npm: 9.2.0 - /opt/homebrew/bin/npm Watchman: 2022.12.12.00 - /opt/homebrew/bin/watchman Browsers: Chrome: 111.0.5563.146 Safari: 16.2 npmPackages: @react-native-async-storage/async-storage: ^1.17.11 => 1.17.11 @react-native-community/netinfo: ^9.3.6 => 9.3.6 amazon-cognito-identity-js: ^5.2.12 => 5.2.12 aws-amplify: ^4.3.43 => 4.3.43 jwt-decode: ^3.1.2 => 3.1.2 react: 18.2.0 => 18.1.0 (16.14.0) react-native: 0.71.4 => 0.70.5 npmGlobalPackages: npm: 9.2.0 ```

Describe the bug

Sometimes Auth.currentSession() throughs an error like "Cannot retrieve a new session. Please authenticate.". For this kind of message, do I really need to re-authenticate anyhow? Like I logged in 7 days ago and my refresh token was going to expire on the 30th day of logging in. Can you please share in which situations this kind of error was raised?

Expected behavior

Auth.currentSession() should able to retrieve a new session if the old session failed due to any reason. Authentication of the user should not be required in this situation.

Reproduction steps

  1. Re-authenticate the user after logout.
  2. Close the application.
  3. Open the application.
  4. await Auth.currentSession() method gets called and throws an error with the message "Cannot retrieve a new session. Please authenticate.".

Code Snippet

// Put your code below this line.

const AWSSessionHandler = async (): Promise<CognitoUserSession | void> => {
  return await Auth.currentSession().then(async data => {
    const updatedToken = data.getAccessToken().getJwtToken();
    await getAuthToken().then(
      async currentToken => currentToken !== updatedToken && (await setAuthToken(updatedToken))
    );
  });
};

await AWSSessionHandler()

Log output

``` // Put your logs below this line Cannot retrieve a new session. Please authenticate. ```

aws-exports.js

No response

Manual configuration

{
  Auth: {
       region: AWS_REGION, // its confidential
       userPoolId: AWS_USERPOOLID, // its confidential
       userPoolWebClientId: AWS_USERPOOLWEBCLIENTID, // its confidential
       mandatorySignIn: false,
       authenticationFlowType: USER_PASSWORD_AUTH,
       storage: MyStorage, // Used react-native-keychain for secure storage instead of AsyncStorage.
  },
}

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

cwomack commented 1 year ago

Hello, @aliza-khu 👋 . This error sounds like the user session may have expired even if you're using the default setting for the refresh token (30 days). Are you only seeing this after multiple days of leaving the app running and/or when closing the app completely as described in your reproduction steps? Or does this happen sporadically during normal use of your app?

It appears that you're also using a custom storage solution with react-native-keychain rather than AsyncStorage. Are you able to share more details about how this is implemented beyond your manual config? I've not used react-native-keychain personally, but it's possible that switching out your custom storage back to the default AsyncStorage that Amplify uses for React Native might be a useful test to help identify if this is what is causing the error.

aliza-khu commented 1 year ago

@cwomack, is this issue and https://github.com/aws-amplify/amplify-js/pull/10831 issue has direct connection? can you please confirm this? if it is then updating the library version(5.0.23) makes auto-resolves the issue but please confirm the latest version of the library should not reopen this issue.

aliza-khu commented 1 year ago

@cwomack, Q: Are you only seeing this after multiple days of leaving the app running and/or when closing the app completely as described in your reproduction steps? Or does this happen sporadically during normal use of your app? A: Cannot say anything maybe or maybe not. I don't have an exact scenario for this.

Q: Are you able to share more details about how this is implemented beyond your manual config? A: Below is the shortcode snap of how I use custom storage:

class MyStorage {
  // the promise returned from sync function
  static syncPromise: Promise<void> = null!;

  static setItem(key: string, value: string): boolean {
    Keychain.setGenericPassword(MEMORY_KEY_PREFIX + key, value, getAuthOptions(key));
    dataMemory[key] = value;

    return dataMemory[key];
  }

  static getItem(key: string): Promise<boolean> {
    return Object.prototype.hasOwnProperty.call(dataMemory, key) ? dataMemory[key] : undefined;
  }

  static removeItem(key: string): boolean {
    Keychain.resetGenericPassword(getAuthOptions(key));

    return delete dataMemory[key];
  }

  static clear(): Record<string, unknown> {
    dataMemory = {};

    return dataMemory;
  }

  static sync(): Promise<void> {
    if (!MyStorage.syncPromise) {
      MyStorage.syncPromise = new Promise<void>((res, rej) => {
        Keychain.getAllGenericPasswordServices().then(keys => {
          const memoryKeys = keys.filter(function (key) {
            return key.startsWith(MEMORY_KEY_PREFIX);
          });

          memoryKeys.map((k, i) => {
            Keychain.getGenericPassword({
              service: k,
              securityLevel: Keychain.SECURITY_LEVEL.SECURE_SOFTWARE,
            })
              .then(credentials => {
                const updatedKey = k.replace(MEMORY_KEY_PREFIX, '');
                dataMemory[updatedKey] = credentials ? credentials.password : {};
                res();
              })
              .catch(error => {
                captureExcptionInSentryWithScope([], error);
                rej(error);
              });
          });
        });

        res();
      });
    }

    return MyStorage.syncPromise;
  }
}

Can you please help me out on this ASAP? I am facing this issue to our production users.

cwomack commented 1 year ago

@aliza-khu, upgrading to the most recent version of Amplify shouldn't cause any issues to upgrade and verify if the fix in #10831 clears this up. Breaking changes don't appear to impact you from what I see in this issue so far for the upgrade to v5, and you can always roll back if something else breaks.

Do you have SSR enabled in your app? And did get a chance to see if changing your storage back to AsyncStorage to see if that stops it?

aliza-khu commented 1 year ago

Hello @cwomack, SSR is not enabled in my app. changing your storage back to AsyncStorage to see if that stops it? - I will check and let you know about that. Till then if using a keychain as storage can be a problem or not, can you check with your team and let me know that. The reason is we believe in security first and AsyncStorage default is not encrypted.

aliza-khu commented 1 year ago

Hello @cwomack, changing your storage back to AsyncStorage to see if that stops it? - The issue occurs very rarely so it had to say.

Can you please share the details like when this "Cannot retrieve a new session. Please authenticate." error happen? What are the causes for the same?

aliza-khu commented 1 year ago

Hello @cwomack, Can you please respond to the above two replies? it seems no response for two days.

cwomack commented 1 year ago

@aliza-khu, apologies on the delayed response as I've been trying to reproduce the issue locally. Thank you for clarifying that SSR isn't enabled.

As to the why/when this error is thrown, the Auth.currentSession() method could throw this error when Amplify cannot retrieve a valid session token from Cognito. This would usually meant the user's authentication state has expired or there's a mismatch for the user's session token with Cognito when trying to validate with the backend.

It's understandable that you're prioritizing security and opted to use react-native-keychain rather than AsyncStorage, but using that package may cause unexpected behavior (as you're seeing with this error) depending on the implementation. You mentioned that you're not sure what scenario is causing this, but is there only one Cognito User Pool at this time? Do you have both "authenticated" and "unauthenticated" roles for users of the app?

I'll review this with our Auth team today to see if there's something tied to the react-native-keychain package that could cause this, but look forward to getting you unblocked ASAP!

aliza-khu commented 1 year ago

@cwomack, Thank you for your effort putting into this for the resolution of the issue. Here is the reply to your query. is there only one Cognito User Pool at this time? - Yes, I am using a single user for authentication at a time. I have one query for you. Please understand the scenario I describe and let me know your thought on the same.

Scenario: User successfully authenticated with Cognito. The user closes the app and opens it after some time. Once the user opens the app, Auth.currentSession() method gets called but before all of that if the sync method of custom storage resolves the promise without the data getting loaded(refer to custom storage snap shared previously). Will this can be the reason that Auth.currentSession() throws "Cannot retrieve a new session. Please authenticate".

aliza-khu commented 1 year ago

@cwomack, Here is the snap of custom storage which I think is the correct one.


static sync(): Promise<void> {
    if (!MyStorage.syncPromise) {
      MyStorage.syncPromise = new Promise<void>((res, rej) => {
        Keychain.getAllGenericPasswordServices()
          .then(keys => {
            const memoryKeys = keys.filter(function (key) {
              return key.startsWith(MEMORY_KEY_PREFIX);
            });

            if (memoryKeys.length === 0) {
              res();
            } else {
              memoryKeys.map((k, i) => {
                Keychain.getGenericPassword({
                  service: k,
                  securityLevel: Keychain.SECURITY_LEVEL.SECURE_SOFTWARE,
                })
                  .then(credentials => {
                    const updatedKey = k.replace(MEMORY_KEY_PREFIX, '');
                    dataMemory[updatedKey] = credentials ? credentials.password : {};
                    i === memoryKeys.length - 1 && res();
                  })
                  .catch(error => {
                    captureExcptionInSentryWithScope([], error);
                    rej(error);
                  });
              });
            }
          })
          .catch(err => {
            captureExcptionInSentryWithScope([], err);
            rej(err);
          });
      });
    }

    return MyStorage.syncPromise;
  }

You can compare this with previously shared custom storage snap.

aliza-khu commented 1 year ago

@cwomack, Can you please verify with your team that custom storage can be the only reason to face the issue "Cannot retrieve a new session. Please authenticate."?

cwomack commented 1 year ago

@aliza-khu, the only time the 'Cannot retrieve a new session. Please authenticate.' error is thrown is when Auth can't get the refreshToken from storage.

const refreshToken = new CognitoRefreshToken({
                RefreshToken: this.storage.getItem(refreshTokenKey),
            });

if (!refreshToken.getToken()) {
                return callback(
                    new Error('Cannot retrieve a new session. Please authenticate.'),
                    null
                );
            }

Is your custom storage cleaning itself out after a set period of time? Or did you set any timeframes for how long things are stored before they expire?

aliza-khu commented 1 year ago

Hello @cwomack, We are cleaning the custom storage when only removeItem method called by aws itself (ref: https://github.com/aws-amplify/amplify-js/issues/11156#issuecomment-1491351128) which is under custom storage. Below is the snap:

static removeItem(key: string): boolean {
    Keychain.resetGenericPassword(getAuthOptions(key));

    return delete dataMemory[key];
  }

And when user logged out I call "Auth.signout();". Please let me know if the app need to remove the custom storage data from another spots.

aliza-khu commented 1 year ago

@cwomack, Can you please reply on the above comments?

aliza-khu commented 1 year ago

@cwomack, I am waiting for your response can you please share your response to the above comments? It's urgent to take action.

cwomack commented 1 year ago

@aliza-khu, appreciate your patience as I'm trying to reproduce and find a workaround that can be posted here either with a sample repo or code that utilizes a similar custom storage w/ react-native-keychain.

aliza-khu commented 1 year ago

@cwomack, Let me know if you need any help from me in replicating the same on your end. But as I said it affects our production users so please keep this in high priority. Your help will guide me into come to any conclusion. 🙂

aliza-khu commented 1 year ago

@cwomack, Any updates?

cwomack commented 1 year ago

@aliza-khu, I was able to implement your custom storage solution that uses react-native-keychain in a React Native app and didn't experience the issue. I don't have any issues using AsyncStorage either (rather than custom storage solution w/ keychain). So far it seems like the next steps to determine what is causing this would be:

  1. Try temporarily swapping out the custom storage for the default AsyncStorage fixes the issue (just to isolate and verify if it's being caused by the custom storage or something local tied to that).

  2. Time how long it is taking until you experience the error again (either by looking at logs or manually timing). I know you mentioned that it happens, "after some time" and that it happens very rarely... but maybe we can see if there's a certain time it takes and dig deeper on the Cognito side to see why the session would be expired/invalid.

  3. Determine if this happens with Android, iOS, or both to see if there are related issues.

  4. Compare our package.json to see if there's a dependency issue that might be causing this. This is what I've got and have tested locally, but can't reproduce the issue:

// package.json dependencies 

  "dependencies": {
    "@aws-amplify/ui-react": "^4.6.0",
    "@react-native-async-storage/async-storage": "^1.18.1",
    "@react-native-community/masked-view": "^0.1.11",
    "@react-native-community/netinfo": "^9.3.9",
    "@react-navigation/native": "^6.1.6",
    "@react-navigation/stack": "^6.3.16",
    "amazon-cognito-identity-js": "^6.2.0",
    "aws-amplify": "^5.1.2",
    "core-js": "^3.30.1",
    "expo": "~48.0.11",
    "expo-status-bar": "~1.4.4",
    "react": "18.2.0",
    "react-native": "0.71.6",
    "react-native-gesture-handler": "^2.9.0",
    "react-native-keychain": "^8.1.1",
    "react-native-reanimated": "^3.0.2",
    "react-native-safe-area-context": "^4.5.1",
    "react-native-screens": "^3.20.0",
    "react-redux": "^8.0.5",
    "redux": "^4.2.1"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0"
aliza-khu commented 1 year ago

@cwomack, Thank you for putting efforts to reproduce the issue. I agree with the determined steps you shared and will try to apply them from my end as well. But before starting that I want to let you know that the issue I was facing is not occurring from my end. The last attempt change I made to fix that issue was updating the custom storage code. I already shared the change in the previous response and here is the link: https://github.com/aws-amplify/amplify-js/issues/11156#issuecomment-1497260879. You can verify the same with your custom storage code w/ keychain. If you found any major change in the custom storage code then please share your custom storage w/ keychain with me(really appreciated). Now to reproduce the issue which I faced 'Cannot retrieve a new session. Please authenticate' You can follow the below steps:

  1. Replace your custom storage code with the very first custom storage snap(ref: https://github.com/aws-amplify/amplify-js/issues/11156#issuecomment-1491351128).
  2. Write some code such that you access the AWS current session(await Auth.currentSession()) right after the app launches.
  3. Sometimes you will receive the error like 'Cannot retrieve a new session. Please authenticate'.

Note: If you are able to successfully reproduce the issue by following the above steps then it clarifies that the issue was not with the library but the way I use the custom storage implementation.

Suggestion: You can check the above scenario on weak internet as well might be that will help you to reproduce the issue in a short time.

Hoping for your positive feedback. 🙂

aliza-khu commented 1 year ago

Hello @cwomack, Can you please share your feedback 👆 ?

aliza-khu commented 1 year ago

Hello @cwomack, No reply since last week 😞. Please do reply at-least.

its-aazizi commented 1 year ago

Facing a similar issue with Amplify version 4.3.43. Haven't provided a Storage option as you can see from my Amplify config below;

Amplify.configure({ identityPoolId, region, userPoolId, userPoolWebClientId: userPoolClientId, oauth: { domain: userDomain, scope: ["email", "openid", "profile"], redirectSignIn: "https://localhost:3000/demo", redirectSignOut: "https://localhost:3000/demo?sign_out=true", responseType: "token" } });

cwomack commented 1 year ago

@aliza-khu, apologies for the delayed response. I had tried to reproduce the issue you're experiencing the Cannot retrieve a new session. Please authenticate error, but haven't been able to yet. I just reviewed your comment again stating that "weak internet" may help in reproducing, so I'll try throttling the network down to 3G speeds and see if I can reproduce. And you're still not certain whether this happens on iOS or Android?

@its-aazizi, are you experiencing this on either iOS or Android specifically?

its-aazizi commented 1 year ago

@cwomack I'm facing this on web.

its-aazizi commented 1 year ago

FireShot Capture 001 - Edit app client information } App client_ user-pool-client } amazon-l_ - ca-central-1 console aws amazon com

Here's a screenshot for my app client settings, for testing purposes I've set the refresh token expiry to an hour, but the issue was still their when the refresh token expiry was set to 30 days, the user would unexpectedly loose the session in an hour or so and get logged out.

aliza-khu commented 1 year ago

@aliza-khu, apologies for the delayed response. I had tried to reproduce the issue you're experiencing the Cannot retrieve a new session. Please authenticate error, but haven't been able to yet. I just reviewed your comment again stating that "weak internet" may help in reproducing, so I'll try throttling the network down to 3G speeds and see if I can reproduce. And you're still not certain whether this happens on iOS or Android?

@its-aazizi, are you experiencing this on either iOS or Android specifically?

It reproduces when you replace the code of your custom storage with mine (https://github.com/aws-amplify/amplify-js/issues/11156#issuecomment-1491351128 - Not platform specific replicated on both the platforms) and try with a bad internet situation.

Note: Please call the API as soon as the app reaches the very first screen.

cwomack commented 1 year ago

@its-aazizi are you experiencing this with both your custom auth flow and the SRP, or just one of them? Also have you tried upgrading to the latest version of Amplify to see if still happens?

cwomack commented 1 year ago

@aliza-khu, I've still been unable to reproduce the issue after throttling down the network speed. I believe you're also on v4.3 of Amplify (similar to @its-aazizi). Are you able to try upgrading to the latest version of Amplify to see if the issues resolves?

If it doesn't, are you able to share a sample repo that implements just the basic logic that reproduce this?

cwomack commented 10 months ago

Closing this issue as we have not heard back from you. If you are still experiencing this, please feel free to reply back and provide any information previously requested and we'd be happy to re-open the issue.

With the release of the latest major version of Amplify (aws-amplify@>6), we'd also highly recommend updating to the most recent version. Please refer to our release announcement, migration guide, and documentation for more information.

Thank you!