aws-amplify / amplify-flutter

A declarative library with an easy-to-use interface for building Flutter applications on AWS.
https://docs.amplify.aws
Apache License 2.0
1.32k stars 247 forks source link

Same token generated for different accounts from the same device #5512

Closed ascentman closed 3 weeks ago

ascentman commented 4 weeks ago

Description

I noticed that the same token (id, access and refresh) is used for different accounts from the same device. I tried to sign in with email then signed out and tried then social sign in with Google using signInWithWebUI. Then for both accounts same token was generated (or created once and then reused).

Documentation is clear about this:

Invoke the signOut api to sign out a user from the Auth category. You can only have one user signed in at a given time. Calling signOut without any options will delete the local cache and keychain of the user and revoke the token if enabled on Amazon Cognito User Pools.

But on practice it wasn't so in my case. I assume that the issue isn't on mobile side as everything is managed by Cognito itself but in our configuration or in Cognito.

Here is our App client information configuration:

Screenshot 2024-10-01 at 14 05 23

Categories

Steps to Reproduce

  1. Sign in with some of accounts (email or social)
  2. Sign out from the app with signOut()
  3. Try to sign in with another account
  4. Fetch active session with:fetchAuthSession and check idToken for both users:
    final authSession = await Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey).fetchAuthSession();
    final tokens = authSession.userPoolTokensResult;
    final idToken = tokens.value.idToken.raw;

Screenshots

No response

Platforms

Flutter Version

3.22.2

Amplify Flutter Version

2.2.0

Deployment Method

Amplify Gen 2

Schema

No response

Equartey commented 3 weeks ago

Hi @ascentman, thanks for reporting this issue. Nothing stands out to me from the screenshot.

We will try to reproduce the behavior.

Can you please provide your Gen 2 Auth definition?

ascentman commented 3 weeks ago

Hi @ascentman, thanks for reporting this issue. Nothing stands out to me from the screenshot.

We will try to reproduce the behavior.

Can you please provide your Gen 2 Auth definition?

thanks for response. Not sure I understand the input I should provide. If you mean config, here it is:

final amplifyconfig = ''' {
  "UserAgent": "aws-amplify-cli/2.0",
  "Version": "1.0",
  "auth": {
    "plugins": {
      "awsCognitoAuthPlugin": {
        "IdentityManager": {
          "Default": {}
        },
        "CognitoUserPool": {
          "Default": {
            "PoolId": "${AppEnv().userPoolIdKey}",
            "AppClientId": "${AppEnv().clientIdKey}",
            "Region": "${AppEnv().regionKey}"
          }
        },
        "Auth": {
          "Default": {
          "OAuth": {
              "WebDomain": "${AppEnv().webDomain}",
              "AppClientId": "${AppEnv().clientIdKey}",
              "SignInRedirectURI": "myapp://",
              "SignOutRedirectURI": "myapp://",
              "responseType": "code",
              "Scopes": [
                    "email",
                    "openid",
                    "profile",
                    "aws.cognito.signin.user.admin"
                    ]
            },
            "authenticationFlowType": "USER_SRP_AUTH",
            "socialProviders": [
                "GOOGLE",
                "APPLE"
            ]
          }
        }
      }
    }
  }
}''';

And here is a part of terraform code for deploying Cognito:

resource "aws_cognito_user_pool" "userpool" {
  name = var.name

  deletion_protection = "ACTIVE"

  # Enable email as the only sign-in option
  username_attributes = ["email"]

  username_configuration {
    case_sensitive = false
  }

  # Automatically verify email addresses
  auto_verified_attributes = ["email"]

  # Disable MFA for this user pool client
  mfa_configuration = "OFF"

  password_policy {
    minimum_length                   = 8
    require_numbers                  = true
    require_lowercase                = false
    require_uppercase                = false
    require_symbols                  = false
    temporary_password_validity_days = 0 
  }

  account_recovery_setting {
    recovery_mechanism {
      name     = "verified_email"
      priority = 1
    }
  }

  admin_create_user_config {
    allow_admin_create_user_only = false #Enable self-registration,Allow users to sign themselves up

  }

  # Email (Standard attribute)
  schema {
    name                     = "email"
    attribute_data_type      = "String"
    developer_only_attribute = false
    mutable                  = true
    required                 = true
    string_attribute_constraints {
      min_length = 3
      max_length = 254
    }
  }

  resource "aws_cognito_identity_provider" "google" {
  user_pool_id  = aws_cognito_user_pool.userpool.id
  provider_name = "Google"
  provider_type = "Google"

  attribute_mapping = {
    email       = "email"
    given_name  = "given_name"
    family_name = "family_name"
    gender      = "genders"
  }

  provider_details = {
    client_id        = local.google_client_id
    client_secret    = local.google_client_secret
    authorize_scopes = "email profile"
  }
}

resource "aws_cognito_user_pool_client" "userpool_client" {
  name                                 = "${var.name}-client"
  user_pool_id                         = aws_cognito_user_pool.userpool.id
  supported_identity_providers         = ["COGNITO", "Google", "SignInWithApple", "Facebook"]
  allowed_oauth_flows_user_pool_client = true # public client
  allowed_oauth_flows                  = ["code"]
  allowed_oauth_scopes                 = ["email", "openid", "profile", "aws.cognito.signin.user.admin"]
  generate_secret                      = false #Don't generate a client secret
  explicit_auth_flows                  = ["ALLOW_USER_SRP_AUTH", "ALLOW_REFRESH_TOKEN_AUTH"]
  refresh_token_validity = 1
  access_token_validity  = 1
  id_token_validity      = 1
  token_validity_units {
    access_token  = "hours"
    id_token      = "hours"
    refresh_token = "hours"
  }
  callback_urls                 = ["myapp://"]
  logout_urls                   = ["myapp://"]
  enable_token_revocation       = true
  prevent_user_existence_errors = "ENABLED"
}
Equartey commented 3 weeks ago

Hi @ascentman, I was interested in seeing your Gen 2 Auth schema definition, similar to what is found in this guide under the section Configure external sign-in backend.

Although it looks like you're using terraform instead, which I suspect is where the issue is.

I could not reproduce the issue on a fresh Gen 2 backend which that uses email and social login auth methods.

One callout, is the following line final idToken = tokens.value.idToken.raw; returns a JWT token. This JWT token contains the same headers definition in all tokens, so the beginning appears identical. However, if the rest of the token is inspected, either by decoding the JWT or visually, they differ in the contents, lengths, etc.

Consider the following to validate the Tokens you are retrieving are identical. Print idToken and locate the output via the Flutter dev tools > logging tab (avoids truncated outputs) and then decoding the token using a tool such as https://jwt.io/ or use a text diff tool.

Please let us know what you find, and if the tokens are indeed identical.

ascentman commented 3 weeks ago

Sorry, @Equartey for bothering you but you helped me to investigate deeper the problem and find a root cause. I compared both raw tokens and they both were the same, but when I started looking into the data it holds - I found that the same data used in both cases (email, name, etc of the first user) and then it was cached in my tokenProvider and wasn't disposed on sign out. So it was my bad, but anyway I am grateful for your support.

github-actions[bot] commented 3 weeks ago

This issue is now closed. Comments on closed issues are hard for our team to see. If you need more assistance, please open a new issue that references this one.