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

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

Open flodaniel opened this issue 2 years ago • 27 comments

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:

  • https://stackoverflow.com/questions/47815161/cognito-auth-flow-fails-with-already-found-an-entry-for-username-facebook-10155
  • https://bobbyhadz.com/blog/aws-cognito-amplify-bad-bugged#oauth-registration-with-amplify

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

  • [ ] Analytics
  • [ ] API (REST)
  • [ ] API (GraphQL)
  • [X] Auth
  • [ ] Authenticator
  • [ ] DataStore
  • [ ] Storage

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

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
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

flodaniel avatar Jun 12 '22 10:06 flodaniel

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?

dnys1 avatar Jun 12 '22 19:06 dnys1

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? :)

flodaniel avatar Jun 12 '22 19:06 flodaniel

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

dnys1 avatar Jun 12 '22 19:06 dnys1

I found a couple mentions of the same issue:

  • https://github.com/aws-amplify/amplify-js/issues/5104
  • https://github.com/aws-amplify/amplify-js/issues/9976

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 avatar Jun 12 '22 21:06 dnys1

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:

  • Sign up user with username/password
  • Get social provider ID (somehow - but not through Hosted UI sign in)
  • Call adminLinkProviderForUser as you have in your lambda
  • Now login with Hosted UI

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).

dnys1 avatar Jun 13 '22 00:06 dnys1

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! :)

flodaniel avatar Jun 13 '22 07:06 flodaniel

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

dnys1 avatar Jun 13 '22 19:06 dnys1

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.

aniketkambli avatar Jun 14 '22 03:06 aniketkambli

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.

dnys1 avatar Jun 14 '22 14:06 dnys1

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.

clementAC avatar Jul 04 '22 15:07 clementAC

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 avatar Jul 07 '22 03:07 frankleng

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.

soplan avatar Jul 18 '22 09:07 soplan

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);
}

mrowles avatar Jul 24 '22 07:07 mrowles

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 avatar Jul 27 '22 17:07 soplan

@soplan yeah pretty much we’re landing

mrowles avatar Jul 27 '22 20:07 mrowles

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.

dnys1 avatar Jul 27 '22 20:07 dnys1

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?

frankleng avatar Aug 01 '22 19:08 frankleng

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 avatar Aug 01 '22 19:08 soplan

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.

frankleng avatar Aug 04 '22 19:08 frankleng

@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" ],

AsitDixit avatar Aug 07 '22 03:08 AsitDixit

@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,

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.

hanna-becker avatar Aug 19 '22 12:08 hanna-becker

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!

dnys1 avatar Aug 19 '22 14:08 dnys1

+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 avatar Aug 19 '22 14:08 Jordan-Nelson

@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 avatar Aug 19 '22 14:08 Jordan-Nelson

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.

Jordan-Nelson avatar Aug 19 '22 14:08 Jordan-Nelson

For microsoft with openID it get worse

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

` //lets assume const userName = "microsoft_1234Ca_sm2332384y24Y"; 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

nuwans avatar Aug 24 '22 08:08 nuwans

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?

pnally-wealth avatar Sep 28 '22 17:09 pnally-wealth

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

frankleng avatar Oct 04 '22 23:10 frankleng

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

NiklasReinhold avatar Dec 12 '22 20:12 NiklasReinhold