amplify-flutter icon indicating copy to clipboard operation
amplify-flutter copied to clipboard

UnknownException with "SignedOutException" on iOS devices

Open ruossn opened this issue 6 months ago • 4 comments

Description

Hi,

We are frequently seeing the following error in our logging tool:

UnknownException {
  "message": "unable to send GraphQLRequest to client.",
  "underlyingException": "SignedOutException {
    \"message\": \"No user is currently signed in\"
  }"
}

This issue occurs almost exclusively on iOS devices. We know that this error can happen, for example, if an account has been deleted or if the refresh token has expired. However, in these cases, we have confirmed that this is not the situation for the affected iOS users. Because of this, we suspect there may be a bug in the iOS implementation of Amplify.

The error is triggered when we attempt to execute a GraphQL query or mutation that requires an authenticated user. In our code, we only perform these operations when we have a valid user profile. This profile is only obtained and stored after a successful sign-in, so we are certain that the user was previously signed in.

For some reason, Amplify appears to "lose" the user on iOS, even though the user had been authenticated earlier.

Categories

  • [ ] Analytics
  • [ ] API (REST)
  • [x] API (GraphQL)
  • [x] Auth
  • [ ] Authenticator
  • [ ] DataStore
  • [ ] Notifications (Push)
  • [ ] Storage

Steps to Reproduce

The issue happens sporadically, so unfortunately, we haven't been able to determine a consistent cause or find a way to reliably reproduce it.

Screenshots

No response

Platforms

  • [x] iOS
  • [ ] Android
  • [ ] Web
  • [ ] macOS
  • [ ] Windows
  • [ ] Linux

Flutter Version

3.27.2

Amplify Flutter Version

2.6.1

Deployment Method

AWS CDK

Schema


ruossn avatar Jun 24 '25 09:06 ruossn

Hello @ruossn, thanks for taking the time to open up this issue. Since the exception is intermittent it might be difficult for us to reproduce and identify the root cause, so if you find a consistent means to reproduce the issue please let us know. In the meantime I'll setup a script to run in the background to try and brute force the exception.

Is it possible the refresh token is being revoked/invalidated by some means such as performing a global signout from another device?

final result = await Amplify.Auth.signOut(
    options: const SignOutOptions(globalSignOut: true),
);

tyllark avatar Jun 25 '25 00:06 tyllark

Hello @ruossn, as an update I've gotten the following errors over the past couple of days, but no SignedOutException.

UnknownException {
    "message": "unable to send GraphQLRequest to client.",
    "underlyingException": "NetworkException {\n  \"message\": \"The request failed due to a network error.\",\n  \"recoverySuggestion\": \"Ensure that you have an active network connection\",\n  \"underlyingException\": \"POST https://cognito-idp.us-west-2.amazonaws.com/? failed: SocketException: Failed host lookup: 'cognito-idp.us-west-2.amazonaws.com' (OS Error: nodename nor servname provided, or not known, errno = 8)\"\n}"
}
GraphQLResponseError{
    "message": "Token has expired.",
    "errorType": "UnauthorizedException"
}

The SignedOutException should only occur when we fail to load credentials from the local Credential Store or when we fail to refresh the access token. Can you please try to reproduce the error locally with additional session logging before/after the failing call. Do not include this logging in production code and do not post your access/refresh tokens here.

  //Don't use in production code as it logs Access/Refresh Token details
  void _logAuthSession() async {
    try {
      final session = await Amplify.Auth.fetchAuthSession();
      session as CognitoAuthSession;

      final accessToken = session.userPoolTokensResult.value.accessToken.raw;
      final refreshToken = session.userPoolTokensResult.value.refreshToken;
      final expiration =
          session.userPoolTokensResult.value.accessToken.claims.expiration;
      final expiresIn = expiration?.difference(DateTime.now());
      safePrint(
        'SessionInfo: ${DateTime.now().toUtc()} - isSignedIn=${session.isSignedIn}, accessToken=${accessToken.hashCode}, refreshToken=${refreshToken.hashCode}, expiresIn=$expiresIn',
      );
    } catch (e) {
      safePrint('SessionInfo: ${DateTime.now().toUtc()}  - exception=$e');
    }
  }

tyllark avatar Jun 26 '25 23:06 tyllark

Hi @tyllark, thanks for the fast response. We were not able to see that error with advanced logging, but we searched more and think currently that this is related to the keystore. We have a custom implementation of SecureStorageInterface, where the tokens are stored in the keystore but are not available when the app is in background. Could it be that the package then dont try to access the token again after that failed multiple times because of that reason? We will try to fix that on our side by grant access to the tokens also if the app is in background, but maybe thats something that could be fixed as well in the package or is worth a note in the docs.

ruossn avatar Jul 04 '25 08:07 ruossn

Hello @ruossn, When we get the current user we don't have any caching mechanism in place on the plugin side, so we read from SecureStorageInterface every time. To determine if the root cause is the implementation of SecureStorageInterface can you:

  1. Temporarily remove your SecureStorageFactory from the AmplifyAuthCognito plugin
  2. Reproduce the issue locally

tyllark avatar Jul 07 '25 20:07 tyllark