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

Merge Accounts in PreSignUp Trigger causes "Already found an entry for username" exception #1716

Closed flodaniel closed 1 year ago

flodaniel commented 2 years ago

Description

When using the following PreSignUp lambda function to merge two accounts (OAuth and native cognito accounts), and allow users to use either options to signup, an exception is raised in the login flow, when the user initially creates their account. After the first sign up, the flow works as expected.

There are stackoverflow threads and blog posts on how to work around this issue, e.g. restarting the auth flow, or just not using adminLinkProviderForUser function:

My PreSignUp trigger is based on: https://bobbyhadz.com/blog/aws-cognito-link-user-accounts

I use an entire custom flow to replace a password-based login with an OTP flow, based on this aws blog post https://aws.amazon.com/de/blogs/mobile/implementing-passwordless-email-authentication-with-amazon-cognito/

Exception: AuthException(message: invalid_request: Already found an entry for username zPW8WMWZaBQLkE9JRMBkonMdvfI1WJ/Ds0K0pgQTM/g=, recoverySuggestion: Retry the webUi signIn, underlyingException: Der Vorgang konnte nicht abgeschlossen werden. (com.amazon.cognito.AWSCognitoAuthErrorDomain-Fehler -3000.))

PreSignUp Trigger lambda function:

import {
  Context,
  PreSignUpTriggerEvent,
  PreSignUpTriggerHandler,
} from "aws-lambda";
import { CognitoIdentityServiceProvider } from "aws-sdk";
import {
  AdminLinkProviderForUserResponse,
  AdminSetUserPasswordResponse,
  ListUsersResponse,
} from "aws-sdk/clients/cognitoidentityserviceprovider";

export const autoConfirmUserHandler: PreSignUpTriggerHandler = async (
  event: PreSignUpTriggerEvent,
  _context: Context
) => {
  const {
    triggerSource,
    userPoolId,
    userName,
    request: {
      // only properties specified as required are available here
      userAttributes: { email },
    },
  } = event;

  const EXTERNAL_AUTHENTICATION_PROVIDER = "PreSignUp_ExternalProvider";

  if (triggerSource === EXTERNAL_AUTHENTICATION_PROVIDER) {
    // --> User has registered with Google/Facebook external providers
    const usersFilteredByEmail = await listUsersByEmail({
      userPoolId,
      email,
    });

    // userName example: "Facebook_12324325436" or "Google_1237823478"
    const [providerNameValue, providerUserId] = userName.split("_");
    // Uppercase the first letter because the event sometimes
    // has it as google_1234 or facebook_1234. In the call to `adminLinkProviderForUser`
    // the provider name has to be Google or Facebook (first letter capitalized)
    const providerName =
      providerNameValue.charAt(0).toUpperCase() + providerNameValue.slice(1);

    if (usersFilteredByEmail.Users && usersFilteredByEmail.Users.length > 0) {
      // user already has cognito account
      const cognitoUsername =
        usersFilteredByEmail.Users[0].Username || "username-not-found";

      // if they have access to the Google / Facebook account of email X, verify their email.
      // even if their cognito native account is not verified
      await adminLinkUserAccounts({
        username: cognitoUsername,
        userPoolId,
        providerName,
        providerUserId,
      });
    } else {
      /* --> user does not have a cognito native account ->
            1. create a native cognito account
            2. change the password, to change status from FORCE_CHANGE_PASSWORD to CONFIRMED
            3. merge the social and the native accounts
            4. add the user to a group - OPTIONAL
        */

      const createdCognitoUser = await adminCreateUser({
        userPoolId,
        email,
      });

      await adminSetUserPassword({ userPoolId, email });

      const cognitoNativeUsername =
        createdCognitoUser.User?.Username || "username-not-found";

      await adminLinkUserAccounts({
        username: cognitoNativeUsername,
        userPoolId,
        providerName,
        providerUserId,
      });

      event.response.autoVerifyEmail = true;
    }
  }

  event.response.autoConfirmUser = true;

  return event;
};

/**
 * Gets a list of users based on an email within a pool
 * @param param0
 * @returns
 */
export const listUsersByEmail = async ({
  userPoolId,
  email,
}: {
  userPoolId: string;
  email: string;
}): Promise<ListUsersResponse> => {
  const params = {
    UserPoolId: userPoolId,
    Filter: `email = "${email}"`,
  };

  const cognitoIdp = new CognitoIdentityServiceProvider();
  return cognitoIdp.listUsers(params).promise();
};

export const adminLinkUserAccounts = async ({
  username,
  userPoolId,
  providerName,
  providerUserId,
}: {
  username: string;
  userPoolId: string;
  providerName: string;
  providerUserId: string;
}): Promise<AdminLinkProviderForUserResponse> => {
  const params = {
    DestinationUser: {
      ProviderAttributeValue: username,
      ProviderName: "Cognito",
    },
    SourceUser: {
      ProviderAttributeName: "Cognito_Subject",
      ProviderAttributeValue: providerUserId,
      ProviderName: providerName,
    },
    UserPoolId: userPoolId,
  };

  const cognitoIdp = new CognitoIdentityServiceProvider();
  return new Promise((resolve, reject) => {
    cognitoIdp.adminLinkProviderForUser(params, (err, data) => {
      if (err) {
        reject(err);
        return;
      }
      resolve(data);
    });
  });
};

/**
 * Creates a cognito user
 * @param param0
 * @returns
 */
export const adminCreateUser = async ({
  userPoolId,
  email,
}: {
  userPoolId: string;
  email: string;
}): Promise<AWS.CognitoIdentityServiceProvider.AdminCreateUserResponse> => {
  const createUserParams = {
    UserPoolId: userPoolId,
    // SUPPRESS prevents sending an email with the temporary password
    // to the user on account creation
    MessageAction: "SUPPRESS",
    Username: email,
    UserAttributes: [
      {
        Name: "email",
        Value: email,
      },
      {
        Name: "email_verified",
        Value: "true",
      },
    ],
  };

  const cognitoIdp = new CognitoIdentityServiceProvider();
  return cognitoIdp.adminCreateUser(createUserParams).promise();
};

/**
 * Sets a random password for an account
 * @param param0
 * @returns
 */
export const adminSetUserPassword = async ({
  userPoolId,
  email,
}: {
  userPoolId: string;
  email: string;
}): Promise<AdminSetUserPasswordResponse> => {
  const params = {
    Password: generatePassword(),
    UserPoolId: userPoolId,
    Username: email,
    Permanent: true,
  };

  const cognitoIdp = new CognitoIdentityServiceProvider();
  return cognitoIdp.adminSetUserPassword(params).promise();
};

/**
 * Generates a random password
 * @returns
 */
function generatePassword() {
  return `${Math.random() // Generate random number, eg: 0.123456
    .toString(36) // Convert  to base-36 : "0.4fzyo82mvyr"
    .slice(-16)}42T`; // Cut off last 16 characters; and add a number and uppercase character to match cognito password policy
}

Categories

Steps to Reproduce

  1. Use provided PreSignUp Trigger
  2. Call the following in your flutter app to sign up with Google/Apple the very first time:
    
      await Amplify.Auth.signInWithWebUI(
        provider: provider,
      );

### Screenshots

_No response_

### Platforms

- [X] iOS
- [ ] Android

### Android Device/Emulator API Level

_No response_

### Environment

```bash
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.0.0, on macOS 12.3.1 21E258 darwin-x64, locale en-GB)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 13.4.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.1)
[✓] IntelliJ IDEA Community Edition (version 2021.3)
[✓] VS Code (version 1.68.0)
[✓] Connected device (3 available)
[✓] HTTP Host Availability

• No issues found!

Dependencies

Click to show ```bash Dart SDK 2.17.0 Flutter SDK 3.0.0 my_app 1.3.1+66 dependencies: - amplify_auth_cognito 0.5.1 [amplify_auth_cognito_android amplify_auth_cognito_ios amplify_auth_plugin_interface amplify_core collection flutter plugin_platform_interface] - amplify_flutter 0.5.1 [amplify_analytics_plugin_interface amplify_api_plugin_interface amplify_auth_plugin_interface amplify_core amplify_datastore_plugin_interface amplify_flutter_android amplify_flutter_ios amplify_storage_plugin_interface collection flutter json_annotation meta plugin_platform_interface] - animate_do 2.1.0 [flutter] - app_settings 4.1.6 [flutter] - auto_size_text 3.0.0 [flutter] - basic_utils 4.2.2 [http logging json_annotation pointycastle] - bloc 8.0.3 [meta] - chopper 4.0.5 [http meta logging] - collection 1.16.0 - connectivity_plus 2.3.0 [flutter connectivity_plus_platform_interface connectivity_plus_linux connectivity_plus_macos connectivity_plus_web connectivity_plus_windows] - device_info_plus 3.2.3 [flutter device_info_plus_platform_interface device_info_plus_macos device_info_plus_linux device_info_plus_web device_info_plus_windows] - dots_indicator 2.1.0 [flutter] - equatable 2.0.3 [collection meta] - firebase_analytics 9.1.8 [firebase_analytics_platform_interface firebase_analytics_web firebase_core firebase_core_platform_interface flutter] - firebase_core 1.17.0 [firebase_core_platform_interface firebase_core_web flutter meta] - firebase_crashlytics 2.8.0 [firebase_core firebase_core_platform_interface firebase_crashlytics_platform_interface flutter stack_trace] - firebase_dynamic_links 4.2.4 [firebase_core firebase_core_platform_interface firebase_dynamic_links_platform_interface flutter meta plugin_platform_interface] - firebase_messaging 11.4.0 [firebase_core firebase_core_platform_interface firebase_messaging_platform_interface firebase_messaging_web flutter meta] - flow_builder 0.0.8 [flutter] - flutter 0.0.0 [characters collection material_color_utilities meta vector_math sky_engine] - flutter_app_badger 1.4.0 [flutter] - flutter_bloc 8.0.1 [flutter bloc provider] - flutter_config 2.0.0 [flutter] - flutter_feather_icons 2.0.0+1 [flutter] - flutter_inappwebview 5.4.3+7 [flutter] - flutter_jailbreak_detection 1.8.0 [flutter] - flutter_keyboard_visibility 5.2.0 [meta flutter_keyboard_visibility_platform_interface flutter_keyboard_visibility_web flutter] - flutter_lints 1.0.4 [lints] - flutter_local_notifications 9.5.3+1 [clock flutter flutter_local_notifications_linux flutter_local_notifications_platform_interface timezone] - flutter_localizations 0.0.0 [flutter intl characters clock collection material_color_utilities meta path vector_math] - flutter_svg 1.0.3 [flutter meta path_drawing vector_math xml] - formz 0.4.1 - functional_widget_annotation 0.9.2 - google_maps_flutter 2.1.5 [flutter flutter_plugin_android_lifecycle google_maps_flutter_platform_interface] - hydrated_bloc 8.1.0 [bloc hive meta synchronized] - implicitly_animated_list 2.1.0 [flutter list_diff] - in_app_purchase 3.0.4 [flutter in_app_purchase_android in_app_purchase_platform_interface in_app_purchase_storekit] - intl 0.17.0 [clock path] - intl_phone_number_input 0.7.0+2 [flutter meta libphonenumber_plugin equatable collection] - json_annotation 4.5.0 [meta] - loader_overlay 2.0.7 [flutter back_button_interceptor] - location 4.4.0 [flutter location_platform_interface location_web] - logger 1.1.0 - maps_toolkit 2.0.0 - material_floating_search_bar 0.3.7 [flutter meta] - package_info_plus 1.4.2 [flutter package_info_plus_platform_interface package_info_plus_linux package_info_plus_macos package_info_plus_windows package_info_plus_web] - permission_handler 9.2.0 [flutter meta permission_handler_android permission_handler_apple permission_handler_windows permission_handler_platform_interface] - pin_code_fields 7.4.0 [flutter] - pull_to_refresh 2.0.0 [flutter] - rflutter_alert 2.0.4 [flutter] - rxdart 0.27.3 - secure_storage 0.0.2 [flutter] - shared_preferences 2.0.15 [flutter shared_preferences_android shared_preferences_ios shared_preferences_linux shared_preferences_macos shared_preferences_platform_interface shared_preferences_web shared_preferences_windows] - sign_in_with_apple 3.3.0 [flutter meta sign_in_with_apple_platform_interface sign_in_with_apple_web] - store_redirect 2.0.1 [flutter] - stream_chat_flutter 3.6.1 [cached_network_image characters chewie collection diacritic dio ezanimation file_picker flutter flutter_markdown flutter_portal flutter_slidable flutter_svg http_parser image_gallery_saver image_picker jiffy lottie meta path_provider photo_manager photo_view rxdart share_plus shimmer stream_chat_flutter_core substring_highlight synchronized url_launcher video_compress video_player video_thumbnail] - stream_chat_localizations 2.1.0 [flutter flutter_localizations stream_chat_flutter] - stream_chat_persistence 3.1.0 [drift flutter logging meta mutex path path_provider sqlite3_flutter_libs stream_chat] - synchronized 3.0.0+2 - tuple 2.0.0 [quiver] - url_launcher 6.1.2 [flutter url_launcher_android url_launcher_ios url_launcher_linux url_launcher_macos url_launcher_platform_interface url_launcher_web url_launcher_windows] - uuid 3.0.6 [crypto] - webview_flutter 3.0.4 [flutter webview_flutter_android webview_flutter_platform_interface webview_flutter_wkwebview] dependency overrides: - flutter_svg 1.0.3 [flutter meta path_drawing vector_math xml] - provider 6.0.3 [collection flutter nested] transitive dependencies: - amplify_analytics_plugin_interface 0.5.1 [amplify_core flutter meta] - amplify_api_plugin_interface 0.5.1 [amplify_core collection flutter json_annotation meta] - amplify_auth_cognito_android 0.5.1 [flutter] - amplify_auth_cognito_ios 0.5.1 [amplify_core flutter] - amplify_auth_plugin_interface 0.5.1 [amplify_core flutter meta] - amplify_core 0.5.1 [collection date_time_format flutter meta plugin_platform_interface uuid] - amplify_datastore_plugin_interface 0.5.1 [flutter meta collection amplify_core] - amplify_flutter_android 0.5.1 [flutter] - amplify_flutter_ios 0.5.1 [amplify_core flutter] - amplify_storage_plugin_interface 0.5.1 [flutter meta amplify_core] - archive 3.3.0 [crypto path] - args 2.3.1 - asn1lib 1.1.0 - async 2.9.0 [collection meta] - back_button_interceptor 6.0.0 [collection flutter] - cached_network_image 3.2.1 [flutter flutter_cache_manager octo_image cached_network_image_platform_interface cached_network_image_web] - cached_network_image_platform_interface 1.0.0 [flutter flutter_cache_manager] - cached_network_image_web 1.0.1 [flutter flutter_cache_manager cached_network_image_platform_interface] - characters 1.2.0 - charcode 1.3.1 - chewie 1.3.3 [cupertino_icons flutter provider video_player wakelock] - clock 1.1.0 - connectivity_plus_linux 1.3.0 [flutter connectivity_plus_platform_interface meta nm] - connectivity_plus_macos 1.2.2 [connectivity_plus_platform_interface flutter] - connectivity_plus_platform_interface 1.2.0 [flutter meta plugin_platform_interface] - connectivity_plus_web 1.2.0 [connectivity_plus_platform_interface flutter_web_plugins flutter] - connectivity_plus_windows 1.2.0 [connectivity_plus_platform_interface flutter] - convert 3.0.1 [typed_data] - cross_file 0.3.3+1 [js meta] - crypto 3.0.2 [typed_data] - crypto_keys 0.3.0 [pointycastle meta collection quiver] - csslib 0.17.1 [source_span] - cupertino_icons 1.0.4 - date_time_format 2.0.1 - dbus 0.7.3 [args ffi meta xml] - device_info_plus_linux 2.1.1 [device_info_plus_platform_interface file flutter meta] - device_info_plus_macos 2.2.3 [device_info_plus_platform_interface flutter] - device_info_plus_platform_interface 2.3.0+1 [flutter meta plugin_platform_interface] - device_info_plus_web 2.1.0 [device_info_plus_platform_interface flutter_web_plugins flutter] - device_info_plus_windows 2.1.1 [device_info_plus_platform_interface ffi flutter win32] - diacritic 0.1.3 - dio 4.0.6 [http_parser path] - drift 1.3.0 [async convert collection meta stream_channel sqlite3] - ezanimation 0.6.0 [flutter] - ffi 1.2.1 - file 6.1.2 [meta path] - file_picker 4.5.1 [flutter flutter_web_plugins flutter_plugin_android_lifecycle plugin_platform_interface ffi path win32] - firebase_analytics_platform_interface 3.1.6 [firebase_core flutter meta plugin_platform_interface] - firebase_analytics_web 0.4.0+13 [firebase_analytics_platform_interface firebase_core firebase_core_web flutter flutter_web_plugins js] - firebase_core_platform_interface 4.4.0 [collection flutter meta plugin_platform_interface] - firebase_core_web 1.6.4 [firebase_core_platform_interface flutter flutter_web_plugins js meta] - firebase_crashlytics_platform_interface 3.2.6 [collection firebase_core flutter meta plugin_platform_interface] - firebase_dynamic_links_platform_interface 0.2.3+2 [firebase_core flutter meta plugin_platform_interface] - firebase_messaging_platform_interface 3.5.0 [firebase_core flutter meta plugin_platform_interface] - firebase_messaging_web 2.4.0 [firebase_core firebase_core_web firebase_messaging_platform_interface flutter flutter_web_plugins js meta] - flutter_blurhash 0.7.0 [flutter] - flutter_cache_manager 3.3.0 [clock collection file flutter http path path_provider pedantic rxdart sqflite uuid] - flutter_keyboard_visibility_platform_interface 2.0.0 [flutter meta plugin_platform_interface] - flutter_keyboard_visibility_web 2.0.0 [flutter_keyboard_visibility_platform_interface flutter_web_plugins flutter] - flutter_local_notifications_linux 0.4.2 [flutter flutter_local_notifications_platform_interface dbus path xdg_directories] - flutter_local_notifications_platform_interface 5.0.0 [flutter plugin_platform_interface] - flutter_markdown 0.6.10+1 [flutter markdown meta path] - flutter_plugin_android_lifecycle 2.0.6 [flutter] - flutter_portal 0.4.0 [flutter] - flutter_slidable 0.6.0 [flutter] - flutter_web_plugins 0.0.0 [flutter js characters collection material_color_utilities meta vector_math] - freezed_annotation 1.1.0 [collection json_annotation meta] - google_maps_flutter_platform_interface 2.1.7 [collection flutter plugin_platform_interface stream_transform] - hive 2.2.1 [meta crypto] - html 0.15.0 [csslib source_span] - http 0.13.4 [async http_parser meta path] - http_parser 4.0.1 [collection source_span string_scanner typed_data] - image_gallery_saver 1.7.1 [flutter] - image_picker 0.8.5+3 [flutter image_picker_android image_picker_for_web image_picker_ios image_picker_platform_interface] - image_picker_android 0.8.4+13 [flutter flutter_plugin_android_lifecycle image_picker_platform_interface] - image_picker_for_web 2.1.8 [flutter flutter_web_plugins image_picker_platform_interface] - image_picker_ios 0.8.5+5 [flutter image_picker_platform_interface] - image_picker_platform_interface 2.5.0 [cross_file flutter http plugin_platform_interface] - in_app_purchase_android 0.2.2+6 [collection flutter in_app_purchase_platform_interface json_annotation] - in_app_purchase_platform_interface 1.3.1 [flutter plugin_platform_interface] - in_app_purchase_storekit 0.3.0+8 [collection flutter in_app_purchase_platform_interface json_annotation] - jiffy 5.0.0 [intl] - jose 0.3.2 [crypto_keys meta typed_data x509 http http_parser asn1lib collection] - js 0.6.4 - libphonenumber 2.0.2 [flutter meta] - libphonenumber_platform_interface 0.3.1 [flutter plugin_platform_interface] - libphonenumber_plugin 0.2.3 [flutter flutter_web_plugins libphonenumber_platform_interface libphonenumber_web libphonenumber] - libphonenumber_web 0.2.0+1 [flutter flutter_web_plugins js libphonenumber_platform_interface] - lints 1.0.1 - list_diff 2.0.1 [async] - location_platform_interface 2.3.0 [flutter meta plugin_platform_interface] - location_web 3.1.1 [flutter flutter_web_plugins http_parser js location_platform_interface meta] - logging 1.0.2 - lottie 1.3.0 [archive flutter path vector_math] - markdown 5.0.0 [args charcode meta] - matcher 0.12.11 [stack_trace] - material_color_utilities 0.1.4 - meta 1.7.0 - mime 1.0.2 - mutex 3.0.0 - nested 1.0.0 [flutter] - nm 0.5.0 [dbus] - octo_image 1.0.2 [flutter flutter_blurhash] - package_info_plus_linux 1.0.5 [package_info_plus_platform_interface flutter path] - package_info_plus_macos 1.3.0 [flutter] - package_info_plus_platform_interface 1.0.2 [flutter meta plugin_platform_interface] - package_info_plus_web 1.0.5 [flutter flutter_web_plugins http meta package_info_plus_platform_interface] - package_info_plus_windows 1.0.5 [package_info_plus_platform_interface ffi flutter win32] - path 1.8.1 - path_drawing 1.0.0 [vector_math meta path_parsing flutter] - path_parsing 1.0.0 [vector_math meta] - path_provider 2.0.10 [flutter path_provider_android path_provider_ios path_provider_linux path_provider_macos path_provider_platform_interface path_provider_windows] - path_provider_android 2.0.14 [flutter path_provider_platform_interface] - path_provider_ios 2.0.9 [flutter path_provider_platform_interface] - path_provider_linux 2.1.6 [ffi flutter path path_provider_platform_interface xdg_directories] - path_provider_macos 2.0.6 [flutter path_provider_platform_interface] - path_provider_platform_interface 2.0.4 [flutter platform plugin_platform_interface] - path_provider_windows 2.0.6 [ffi flutter path path_provider_platform_interface win32] - pedantic 1.11.1 - permission_handler_android 9.0.2+1 [flutter permission_handler_platform_interface] - permission_handler_apple 9.0.4 [flutter permission_handler_platform_interface] - permission_handler_platform_interface 3.7.0 [flutter meta plugin_platform_interface] - permission_handler_windows 0.1.0 [flutter permission_handler_platform_interface] - petitparser 5.0.0 [meta] - photo_manager 2.1.1 [flutter] - photo_view 0.13.0 [flutter] - platform 3.1.0 - plugin_platform_interface 2.1.2 [meta] - pointycastle 3.6.0 [collection convert js] - process 4.2.4 [file path platform] - quiver 3.1.0 [matcher] - rate_limiter 0.1.1 - share_plus 4.0.4 [meta mime flutter share_plus_platform_interface share_plus_linux share_plus_macos share_plus_windows share_plus_web] - share_plus_linux 3.0.0 [share_plus_platform_interface file flutter meta url_launcher] - share_plus_macos 3.0.0 [share_plus_platform_interface flutter] - share_plus_platform_interface 3.0.2 [flutter meta mime plugin_platform_interface] - share_plus_web 3.0.0 [share_plus_platform_interface url_launcher flutter flutter_web_plugins meta] - share_plus_windows 3.0.0 [share_plus_platform_interface flutter meta url_launcher] - shared_preferences_android 2.0.12 [flutter shared_preferences_platform_interface] - shared_preferences_ios 2.1.1 [flutter shared_preferences_platform_interface] - shared_preferences_linux 2.1.1 [file flutter path path_provider_linux path_provider_platform_interface shared_preferences_platform_interface] - shared_preferences_macos 2.0.4 [flutter shared_preferences_platform_interface] - shared_preferences_platform_interface 2.0.0 [flutter] - shared_preferences_web 2.0.4 [flutter flutter_web_plugins shared_preferences_platform_interface] - shared_preferences_windows 2.1.1 [file flutter path path_provider_platform_interface path_provider_windows shared_preferences_platform_interface] - shimmer 2.0.0 [flutter] - sign_in_with_apple_platform_interface 1.0.0 [flutter plugin_platform_interface meta] - sign_in_with_apple_web 1.0.1 [flutter flutter_web_plugins sign_in_with_apple_platform_interface js] - sky_engine 0.0.99 - source_span 1.9.0 [collection path term_glyph] - sqflite 2.0.2+1 [flutter sqflite_common path] - sqflite_common 2.2.1+1 [synchronized path meta] - sqlite3 1.7.1 [collection ffi js meta path] - sqlite3_flutter_libs 0.5.7 [flutter] - stack_trace 1.10.0 [path] - stream_channel 2.1.0 [async] - stream_chat 3.6.1 [async collection dio equatable freezed_annotation http_parser jose json_annotation logging meta mime rate_limiter rxdart uuid web_socket_channel] - stream_chat_flutter_core 3.6.1 [collection connectivity_plus flutter meta rxdart stream_chat] - stream_transform 2.0.0 - string_scanner 1.1.1 [source_span] - substring_highlight 1.0.33 [flutter] - term_glyph 1.2.0 - timezone 0.8.0 [path] - typed_data 1.3.1 [collection] - url_launcher_android 6.0.17 [flutter url_launcher_platform_interface] - url_launcher_ios 6.0.17 [flutter url_launcher_platform_interface] - url_launcher_linux 3.0.1 [flutter url_launcher_platform_interface] - url_launcher_macos 3.0.1 [flutter url_launcher_platform_interface] - url_launcher_platform_interface 2.0.5 [flutter plugin_platform_interface] - url_launcher_web 2.0.11 [flutter flutter_web_plugins url_launcher_platform_interface] - url_launcher_windows 3.0.1 [flutter url_launcher_platform_interface] - vector_math 2.1.2 - video_compress 3.1.0 [flutter] - video_player 2.4.2 [flutter html video_player_android video_player_avfoundation video_player_platform_interface video_player_web] - video_player_android 2.3.4 [flutter video_player_platform_interface] - video_player_avfoundation 2.3.4 [flutter video_player_platform_interface] - video_player_platform_interface 5.1.2 [flutter plugin_platform_interface] - video_player_web 2.0.10 [flutter flutter_web_plugins video_player_platform_interface] - video_thumbnail 0.5.0 [flutter] - wakelock 0.6.1+2 [flutter meta wakelock_macos wakelock_platform_interface wakelock_web wakelock_windows] - wakelock_macos 0.4.0 [flutter flutter_web_plugins wakelock_platform_interface] - wakelock_platform_interface 0.3.0 [flutter meta] - wakelock_web 0.4.0 [flutter flutter_web_plugins js wakelock_platform_interface] - wakelock_windows 0.2.0 [flutter wakelock_platform_interface win32] - web_socket_channel 2.2.0 [async crypto stream_channel] - webview_flutter_android 2.8.8 [flutter webview_flutter_platform_interface] - webview_flutter_platform_interface 1.9.0 [flutter meta plugin_platform_interface] - webview_flutter_wkwebview 2.7.5 [flutter path webview_flutter_platform_interface] - win32 2.6.1 [ffi] - x509 0.2.2 [asn1lib quiver crypto_keys] - xdg_directories 0.2.0+1 [meta path process] - xml 5.4.1 [collection meta petitparser] ```

Device

iPhone SE

OS

iOS 15.5

CLI Version

7.6.22

Additional Context

No response

dnys1 commented 2 years ago

Hi @flodaniel - sorry you are experiencing this issue. Changing the following:

await adminLinkUserAccounts({
  username: cognitoUsername,
  userPoolId,
  providerName,
  providerUserId,
});

to this:

await adminLinkUserAccounts({
  username: cognitoUsername,
  userPoolId,
  providerName,
  providerUserId: userName,
});

seemed to fix the problem for me. Can you try that and let me know if you run into any other issues?

flodaniel commented 2 years ago

Hi @dnys1 ,

thanks for the quick reply and specific change suggestion! While the changes fixes the immediate issue, it disables the intended behaviour. I now get two, unlinked cognito users, and when trying to reuse the email, which was initially used for sign up with google, for sign in with email, I have the same issue as without the lambda function --> The user cannot sign in, because he initially used google sign-in and in our database we only have the cognito userid of the google user.

These are the two accounts created, which are not linked, and do not share the sub property:

image

This is what we have in our database:

image

Any other suggestions? :)

dnys1 commented 2 years ago

Ah, sorry about that. I missed that difference between the two IDs. I will keep digging!

dnys1 commented 2 years ago

I found a couple mentions of the same issue:

It looks like many people have implemented a front-end check to catch the error thrown and retry since it will succeed the second time. Not a great solution, and I will keep searching, but could be a way to work around the issue in the meantime!

dnys1 commented 2 years ago

Okay, so this is a weird one. Here are my findings so far:

w/out Triggers

First, I tried to reproduce this without triggers and following the federation instructions in Cognito's docs. The flow for this is:

This works and creates a single user with the social provider ID in the identities map. Logging in with either username/password or Hosted UI returns the same sub.

Reproducing with Triggers

The problem seems to be that, with triggers, the only time you have knowledge of the social provider ID is after a sign in/up is already initiated when you really want it before the sign in initiates. So, to work around this, as others have suggested, you can throw a special error after calling adminLinkProviderForUser in your lambda and then catch this error in your front end, perhaps show a message like "Accounts have been successfully linked" and then prompt users to re-login with Hosted UI. This time the pre-signup lambda will not be invoked and login will continue normally.

Obviously, this is a suboptimal pattern from a UX perspective, and I will be asking the Cognito team their thoughts on this issue. However, in the meantime, this seems to be the only solution available (unless you know the social provider ID beforehand).

p.s. if you see Invalid ProviderName/Username combination. error, this is because the casing of the ProviderAttributeValue for SourceUser (i.e. the social provider ID) has to exactly match what's in the token issued by the social provider. For some reason, this is not always passed correctly into the lambda. (Will ask the Cognito team about this as well).

flodaniel commented 2 years ago

Thanks for doing your own in-depth research on this issue! Really appreciated.

The hacky work around is not an option for us. We already have to deal with the overall bad experience with the hosted web UI instead of native dialogs, and further complicating that user flow is not really something we want to do.

Will you report back in this ticket once you hear from the Cognito team? From what I have read so far the "Cognito Team" is the black hole for all bugs that are caused by the Cognito side and that will never be fixed - or no clear roadmap on when the fix is going to happen. For a paid service really disappointing.

Again, appreciate your input and happy that the amplify team is doing a good job here! :)

dnys1 commented 2 years ago

I will keep you posted on any developments. Please let me know if you'd like to brainstorm more alternatives in the meantime!

aniketkambli commented 2 years ago

hey @dnys1 , i am facing the exact same issue while using google login + cognito in a react web app. I am also using presignup trigger to merge the two user accounts and keep the sub same, let me know if you find any workaround this.

dnys1 commented 2 years ago

Hi @aniketkambli - I'm sorry you are also facing this issue. At the moment, the only known workaround is the one mentioned above to catch a unique error thrown from your lambda and retry the login.

clementAC commented 2 years ago

same problem here. As amplify is designed to simplify AWS services usage it could be awesome to have in the federatedSignIn an option like mergeExistingAccount:true/false. There is almost no use case in modern application where you want your user to create multiple accounts with the same email from different auth provider.

frankleng commented 2 years ago

btw - the workaround doesn't work. it might for Google (tho the error caught wasn't specific enough for us to distinguish), but certainly does not for Apple, FB, etc. Ultimately we removed Cognito altogether, and moved to Firebase Auth - much better API, auto account merging. if you use API Gateway > Lambda: you can use the same AWS OAuth authorizer. Took a whole day of work, but worth it.

soplan commented 2 years ago

We would also like to have this fixed. Currently having Google, Apple and Microsoft social login so the proposed work around will also not work for us.

Please have this fixed, else more developers will decide to use Firebase instead of Cognito.

mrowles commented 2 years ago

Any updates on this? Any help required with testing?

I used to be able to handle this on the client by catching the error and reauthorising (shout out to Bobby Hadz), but the error no longer contains 'Google', 'Facebook' etc. and more like the hash you provided above (Already found an entry for username zPW8WMWZaBQLkE9JRMBkonMdvfI1WJ/Ds0K0pgQTM/g=)

if (
  authProviderUsernameError &&
  /already.found.an.entry.for.username.google/gi.test(
    authProviderUsernameError.toString(),
  )
) {
  onSignIn(CognitoHostedUIIdentityProvider.Google);
} else if (
  authProviderUsernameError &&
  /already.found.an.entry.for.username.facebook/gi.test(
    authProviderUsernameError.toString(),
  )
) {
  onSignIn(CognitoHostedUIIdentityProvider.Facebook);
}
soplan commented 2 years ago

We also have a challenge now how to handle this in a nice way. We are thinking about using sessions storage to remember which social login button the user used. It's not great...

mrowles commented 2 years ago

@soplan yeah pretty much we’re landing

dnys1 commented 2 years ago

This is an issue that needs to be fixed in Cognito itself. While it's on their radar, I have no information on when it might be fixed.

frankleng commented 2 years ago

We also have a challenge now how to handle this in a nice way. We are thinking about using sessions storage to remember which social login button the user used. It's not great...

@soplan even if u store what user clicked, don't they still have to oauth again?

soplan commented 2 years ago

We also have a challenge now how to handle this in a nice way. We are thinking about using sessions storage to remember which social login button the user used. It's not great...

@soplan even if u store what user clicked, don't they still have to oauth again?

Yes but we do the reauth automatically. So the user will only see the screen flashing twice.

It’s Terrible. I hate it. But the benefits for the user having the account linked is higher than the user getting into a new account and thinking we deleted their account or something else happened that erased their data.

Knowing this about cognito, I would have chosen for a different auth provider. Migrating is not an option for us now. Too much work and risk involved in switching to firebase or another party.

frankleng commented 2 years ago

We also have a challenge now how to handle this in a nice way. We are thinking about using sessions storage to remember which social login button the user used. It's not great...

@soplan even if u store what user clicked, don't they still have to oauth again?

Yes but we do the reauth automatically. So the user will only see the screen flashing twice.

It’s Terrible. I hate it. But the benefits for the user having the account linked is higher than the user getting into a new account and thinking we deleted their account or something else happened that erased their data.

Knowing this about cognito, I would have chosen for a different auth provider. Migrating is not an option for us now. Too much work and risk involved in switching to firebase or another party.

@soplan which oauth providers? when we tested - only google sorted of worked by calling auth again without user having to click thru. but even google did not work consistently.

AsitDixit commented 2 years ago

@dnys1 can you tell me in my code why this portion is not working: `if (listUsersRes.Users.length === 0) { const {email = '',} = event.request.userAttributes; const newPassword = uuidv4(); const newUserParams = { UserPoolId: event.userPoolId, Username: event.userName, MessageAction: 'SUPPRESS', TemporaryPassword: newPassword, UserAttributes: [ { Name: 'email', Value: email, }, { Name: 'email_verified', Value: String(!!email), }, ], };

            const newUser = await cognitoProvider.adminCreateUser(newUserParams).promise();

            // Confirm new user
            const setPasswordParams = {
                Password: newPassword,
                UserPoolId: event.userPoolId,
                Username: newUser.User.Username,
                Permanent: true,
            };
            await cognitoProvider.adminSetUserPassword(setPasswordParams).promise();

            destinationAttributeValue = newUser.User.Username;
        }`

And my permissions are: "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents", "cognito-idp:AdminInitiateAuth", "cognito-idp:ListUsers", "cognito-idp:AdminUpdateUserAttributes", "cognito-idp:AdminLinkProviderForUser", "cognito-idp:AdminCreateUser", "cognito-idp:AdminSetUserPassword" ],

pierrick-libert-codeleap commented 2 years ago

@dnys1 Hello,

Is there a github issue/thread created and assigned to the Cognito team where we could follow-up instead of bothering you here? If no, where should I/we create one?

Thank you,

hanna-becker commented 2 years ago

Thanks to the suggestions mentioned in earlier posts, we managed to implement merging accounts for our application. As suggested, we throw a custom exception from the Presignup trigger function to prevent creation of a separate user, and that way the social logins only show up in the identities array of the native Cognito user. We include the provider name in the exception's message, so that the client can reinitialize the flow for that provider. This works fairly okay for our Angular web app client (fairly, because it fails when a user has e.g. more than one Google profile stored in their browser).

[*** Side note: There were a bunch of other workarounds necessary, e.g. we keep setting email_confirmed back to true in a PostAuthentication trigger, otherwise the user won't be able to use the forgot password flow for their native Cognito account, as it gets set back to false on every social provider login. And yes, Cognito has an attribute mapping option for this for Google, but not for Facebook, unfortunately. And we have to call our PostConfirmation signup trigger manually, because the Cognito admin function for confirming a user won't trigger it.]

HOWEVER, we recently started adding a mobile flutter client to our application. The issue is, we cannot catch our custom exception there, as it gets passed through the hosted web UI, which then only returns a generic exception to our Flutter client:

E/amplify:flutter:auth_cognito( 3431): AuthException E/amplify:flutter:auth_cognito( 3431): AuthException{message=Sign-in with web UI failed, cause=com.amazonaws.mobileconnectors.cognitoauth.exceptions.AuthServiceException: invalid_request, recoverySuggestion=See attached exception for more details} E/amplify:flutter:auth_cognito( 3431): at com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin$23.onError(AWSCognitoAuthPlugin.java:1275)E/amplify:flutter:auth_cognito( 3431): at com.amazonaws.mobile.client.internal.InternalCallback.call(InternalCallback.java:77) E/amplify:flutter:auth_cognito( 3431): at com.amazonaws.mobile.client.internal.InternalCallback.onError(InternalCallback.java:67) E/amplify:flutter:auth_cognito( 3431): at com.amazonaws.mobile.client.AWSMobileClient$27$1$3.run(AWSMobileClient.java:3544) E/amplify:flutter:auth_cognito( 3431): at java.lang.Thread.run(Thread.java:923) E/amplify:flutter:auth_cognito( 3431): Caused by: com.amazonaws.mobileconnectors.cognitoauth.exceptions.AuthServiceException: invalid_request E/amplify:flutter:auth_cognito( 3431): at com.amazonaws.mobileconnectors.cognitoauth.AuthClient$1$2.run(AuthClient.java:456) E/amplify:flutter:auth_cognito( 3431): at android.os.Handler.handleCallback(Handler.java:938) E/amplify:flutter:auth_cognito( 3431): at android.os.Handler.dispatchMessage(Handler.java:99) E/amplify:flutter:auth_cognito( 3431): at android.os.Looper.loop(Looper.java:223) E/amplify:flutter:auth_cognito( 3431): at android.app.ActivityThread.main(ActivityThread.java:7656) E/amplify:flutter:auth_cognito( 3431): at java.lang.reflect.Method.invoke(Native Method) E/amplify:flutter:auth_cognito( 3431): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) E/amplify:flutter:auth_cognito( 3431): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

I believe this situation is somewhat similar to the original issue mentioned here. Even if Cognito doesn't implement a feature for auto-merging accounts any time soon, it would be helpful to at least have a way to access the original exceptions coming from the backend in the client.

dnys1 commented 2 years ago

Hi @hanna-becker, there is currently an open issue in Android to fix this https://github.com/aws-amplify/amplify-android/issues/1649. Unfortunately, I don't have a timeline on when that could happen.

If you're able to, I would recommend trying our dev-preview rewrite which surfaces all lambda exceptions. While it is not currently production ready, we will be shifting our focus to development there and can prioritize fixes like this much quicker for our customers.

Hope this helps!

Jordan-Nelson commented 2 years ago

+1 to @dnys1's suggestion about the dev-preview version. If you are able to try it out, we would be interested in any feedback you have.

Also, I just wanted to add a link to the issue in amplify-flutter where the Hosted UI & Lambda exceptions is being tracked - https://github.com/aws-amplify/amplify-flutter/issues/1279. If you find any issues with Lambda exceptions and Hosted UI in dev-preview, feel free to leave feedback on that issue.

You can read more about the developer preview release in the blog post and docs.

Jordan-Nelson commented 2 years ago

@pierrick-libert-codeleap - I don't have a GitHub issue to link you to unfortunately. We do pass this feedback along to the Cognito team and they are aware it is something that customers are asking for. I unfortunately do not have a timeline on when it will be supported natively by Cognito though.

Jordan-Nelson commented 2 years ago

I would also encourage anyone who is interested in seeing support for this feature to give the original issue description a 👍 . While hearing the individual use cases can be helpful, we use the number of 👍 reactions to an issue to get a quick idea of how much interest there is in a feature.

nuwans commented 2 years ago

For microsoft with openID it get worse

If we are redirecting from microsoft userName sometimes looks like "microsoft_CAPITALsimplewithSomething" **Yes there are capital letters and simple letters and ' ' as well ** as a example

` //lets assume const userName = "microsoft_1234Casm2332384y24Y"; const [providerNameValue, providerUserId] = userName.split("");

//providerUserId will be "1234Ca" //as you can see "sim2332384y24Y" part is missing //this never happens with google because "Google_1237823478" only has numbers. ` Then another record in cognito users will be created. They are linked but sub properties are different Also identities["userId":"12dwded"] in user records are different .

PS. Just mentioning these because someone might save lot of time

pnally-wealth commented 2 years ago

btw - the workaround doesn't work. it might for Google (tho the error caught wasn't specific enough for us to distinguish), but certainly does not for Apple, FB, etc. Ultimately we removed Cognito altogether, and moved to Firebase Auth - much better API, auto account merging. if you use API Gateway > Lambda: you can use the same AWS OAuth authorizer. Took a whole day of work, but worth it.

@frankleng What were some of the pitfalls you have to figure out when moving from Cognito to Firebase?

frankleng commented 2 years ago

btw - the workaround doesn't work. it might for Google (tho the error caught wasn't specific enough for us to distinguish), but certainly does not for Apple, FB, etc. Ultimately we removed Cognito altogether, and moved to Firebase Auth - much better API, auto account merging. if you use API Gateway > Lambda: you can use the same AWS OAuth authorizer. Took a whole day of work, but worth it.

@frankleng What were some of the pitfalls you have to figure out when moving from Cognito to Firebase?

it was a fairly straight forward transition. u can import existing user Ids into Firebase auth, and swap UI libs. user did not notice a difference, other than not having to auth twice. only thing was rewriting our cloudflare jwt middleware to use firebase - https://github.com/frankleng/cloudflare-firebase-jwt-verifier

NiklasReinhold commented 1 year ago

It is almost end of year and no solution found!?

flodaniel commented 1 year ago

I have opened a support case with the Cognito team about this to get more info about the status of this issue and will edit this comment once i have received a reply.

Edit: After some back and forth the support also refers to the proposed "solution" of doing the auth flow twice and showing a "linking accounts successful" message. An alternative approach proposed by the support is the answer in this re:post thread: https://repost.aws/questions/QUgWVkIodQS1W3Yj8MYjInbA/cognito-auth-flow-fails-with-already-found-an-entry-for-username-username I tried implementing it, with help from support, but the description on how this should work is a mess and I am unsure if this would ever work. If anyone figures it out, please share. I can also give some insights where i got stuck.

Overall they also referred to this being a "feature" that the cognito team is aware of, but they refused to give a timeline or any indication if they will ever work on it. For a product that we are paying for, this lack of transparency has been the most disappointing aspect so far.

slaat commented 1 year ago

I just came across this issue, how is this still a problem?

NiklasReinhold commented 1 year ago

I just came across this issue, how is this still a problem?

@slaat Yes this is still a problem in productive! Whenever the function adminLinkProviderForUser is called in the preSignUp-Trigger the login fails. This is a really big problem and leads to a really bad user experience, since they have to login two! times.
(first for account linking -> then error, second to login)

I am really disappointed that amplify/cognito has no solution for such a basic need. Would we have known that, we would have used firebase auth.

nikvin15 commented 1 year ago

Is this issue fixed somehow ?? I was facing this exact issue since couple of days back and today I was testing and found it works ! Can we confirm if there is any changes deployed ??

nikvin15 commented 1 year ago

Fyi @dnys1 @Jordan-Nelson

dnys1 commented 1 year ago

Hi @nikvin15 I'm not aware of any changes deployed which should impact this.

abdallahshaban557 commented 1 year ago

Cognito has recently resolved an issue with linking federated users to an existing user profile in Cognito User Pools. As a result, your Amplify application can now merge user identities from 3rd party social identity providers to a Cognito user in a user pool using the Pre sign-up Lambda trigger. You can use the AdminLinkProviderForUser API from Cognito to set up custom logic to determine how your user identities get merged. To learn more about the use of this feature, you can check out the following documentation pages.

We will work on resources to share with you to explain how to integrate this feature into your Amplify app.

flodaniel commented 1 year ago

I can confirm that my above implementation now works on the first try and we do not have to restart the authentication flow in the app anymore. A huge shoutout to everbody on the AWS team that made this possible 👍 💯

Closing this issue.

abdallahshaban557 commented 1 year ago

@flodaniel - glad to hear this resolves your issue!

soplan commented 1 year ago

Can confirm also works for Google linking.

Microsoft & Apple not. I hope cognito team pick this up as their next priority as firebase already supports linking these other social logins.

abdallahshaban557 commented 1 year ago

Hi @soplan - what is the error you get for microsoft and Apple linking? can you please create a new Github issue so that we can track those specific failures?

hanna-becker commented 1 year ago

@abdallahshaban557 How do we set email_verified to true for Facebook social login now? As there is no attribute mapping available for email_verified in the case of Facebook, like many others I used this hacky workaround to set it to true each time the PostAuthentication lambda ran: https://stackoverflow.com/questions/68859052/aws-cognito-email-unverified-on-main-account-after-adminlinkproviderforuser

With this issue fixed, we don't need to retrigger the auth flow on the client, which also means we don't enter that function any longer. However, setting email_verified to true in the PreSignup trigger doesn't work, unfortunately. I guess it gets overridden somewhere? So confused...

abdallahshaban557 commented 1 year ago

Hi @hanna-becker - so the email_verified flag is being set to "false" after the linking is complete? Can you please create a new Github issue so that we can triage this further and raise it to the Cognito team if needed. That definetely seems like odd behavior.

hanna-becker commented 1 year ago

I raised a new issue on this and all other pain points I came across when implementing this: https://github.com/aws-amplify/amplify-js/issues/11565

Everyone who ran into any of those issues, please upvote so that we get a better developer experience in the future.