aws-amplify / amplify-js

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

User is created in Authentication before user is confirmed with verification #13028

Closed ChristopherGabba closed 8 months ago

ChristopherGabba commented 8 months ago

Before opening, please confirm:

JavaScript Framework

React Native

Amplify APIs

Authentication

Amplify Version

v6

Amplify Categories

auth

Backend

Amplify CLI

Environment information

``` # Put output below this line System: OS: macOS 14.2.1 CPU: (10) arm64 Apple M2 Pro Memory: 427.09 MB / 16.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 20.7.0 - /opt/homebrew/bin/node npm: 10.1.0 - /opt/homebrew/bin/npm Watchman: 2023.09.04.00 - /opt/homebrew/bin/watchman Browsers: Safari: 17.2.1 npmPackages: @aws-amplify/react-native: ^1.0.12 => 1.0.12 @babel/core: ^7.20.0 => 7.23.7 @babel/plugin-proposal-export-namespace-from: ^7.18.9 => 7.18.9 @babel/preset-env: ^7.20.0 => 7.23.8 @babel/runtime: ^7.20.0 => 7.23.8 @expo-google-fonts/m-plus-1p: ^0.2.3 => 0.2.3 @expo-google-fonts/montserrat: ^0.2.3 => 0.2.3 @expo-google-fonts/space-grotesk: ^0.2.2 => 0.2.3 @expo/config-plugins: ~7.2.2 => 7.2.5 (5.0.4) @expo/metro-config: ^0.10.7 => 0.10.7 @gorhom/bottom-sheet: ^4.4.7 => 4.6.0 @likashefqet/react-native-image-zoom: ^2.1.1 => 2.1.1 @react-native-async-storage/async-storage: ^1.18.2 => 1.21.0 @react-native-clipboard/clipboard: ^1.11.2 => 1.13.2 @react-native-community/cli-platform-ios: ^8.0.2 => 8.0.6 (11.3.7) @react-native-community/netinfo: ^9.3.10 => 9.5.0 @react-navigation/bottom-tabs: ^6.3.2 => 6.5.11 @react-navigation/native: ^6.0.2 => 6.1.9 @react-navigation/native-stack: ^6.0.2 => 6.9.17 @rnx-kit/metro-config: ^1.3.5 => 1.3.14 @rnx-kit/metro-resolver-symlinks: ^0.1.26 => 0.1.34 @sentry/react-native: 5.10.0 => 5.10.0 @shopify/flash-list: 1.4.3 => 1.4.3 @types/i18n-js: 3.8.2 => 3.8.2 @types/jest: ^29.2.1 => 29.5.11 @types/lodash.filter: ^4.6.9 => 4.6.9 @types/react: ~18.2.14 => 18.2.48 @types/react-test-renderer: ^18.0.0 => 18.0.7 @typescript-eslint/eslint-plugin: ^5.59.0 => 5.62.0 @typescript-eslint/parser: ^5.59.0 => 5.62.0 HelloWorld: 0.0.1 apisauce: 2.1.5 => 2.1.5 aws-amplify: ^6.0.12 => 6.0.12 aws-amplify/adapter-core: undefined () aws-amplify/analytics: undefined () aws-amplify/analytics/kinesis: undefined () aws-amplify/analytics/kinesis-firehose: undefined () aws-amplify/analytics/personalize: undefined () aws-amplify/analytics/pinpoint: undefined () aws-amplify/api: undefined () aws-amplify/api/server: undefined () aws-amplify/auth: undefined () aws-amplify/auth/cognito: undefined () aws-amplify/auth/cognito/server: undefined () aws-amplify/auth/enable-oauth-listener: undefined () aws-amplify/auth/server: undefined () aws-amplify/datastore: undefined () aws-amplify/in-app-messaging: undefined () aws-amplify/in-app-messaging/pinpoint: undefined () aws-amplify/push-notifications: undefined () aws-amplify/push-notifications/pinpoint: undefined () aws-amplify/storage: undefined () aws-amplify/storage/s3: undefined () aws-amplify/storage/s3/server: undefined () aws-amplify/storage/server: undefined () aws-amplify/utils: undefined () axios: ^1.5.0 => 1.6.5 (0.21.4) babel-jest: ^29.2.1 => 29.7.0 babel-loader: 8.2.5 => 8.2.5 babel-plugin-root-import: ^6.6.0 => 6.6.0 cheerio: ^1.0.0-rc.12 => 1.0.0-rc.12 core-util-is: 1.0.1 date-fns: ^2.29.2 => 2.30.0 eslint: 8.17.0 => 8.17.0 eslint-config-prettier: 8.5.0 => 8.5.0 eslint-config-standard: 17.0.0 => 17.0.0 eslint-plugin-import: 2.26.0 => 2.26.0 eslint-plugin-n: ^15.0.0 => 15.7.0 eslint-plugin-node: 11.1.0 => 11.1.0 eslint-plugin-promise: 6.0.0 => 6.0.0 eslint-plugin-react: 7.30.0 => 7.30.0 eslint-plugin-react-native: 4.0.0 => 4.0.0 expo: ^49.0.21 => 49.0.21 expo-application: ~5.3.0 => 5.3.1 expo-blur: ~12.4.1 => 12.4.1 expo-build-properties: ~0.8.3 => 0.8.3 expo-clipboard: ~4.3.1 => 4.3.1 expo-config-plugin-ios-share-extension: ^0.0.4 => 0.0.4 expo-constants: ~14.4.2 => 14.4.2 expo-contacts: ~12.2.0 => 12.2.0 expo-dev-client: ~2.4.11 => 2.4.12 expo-device: ~5.4.0 => 5.4.0 expo-file-system: ~15.4.4 => 15.4.5 expo-font: ~11.4.0 => 11.4.0 expo-image: ~1.3.5 => 1.3.5 expo-image-picker: ~14.3.2 => 14.3.2 expo-linear-gradient: ~12.3.0 => 12.3.0 expo-linking: ~5.0.2 => 5.0.2 expo-localization: ~14.3.0 => 14.3.0 expo-media-library: ~15.4.1 => 15.4.1 expo-notifications: ~0.20.1 => 0.20.1 expo-secure-store: ~12.3.1 => 12.3.1 expo-splash-screen: ^0.20.5 => 0.20.5 expo-status-bar: ~1.6.0 => 1.6.0 expo-store-review: ~6.4.0 => 6.4.0 expo-video-thumbnails: ~7.4.0 => 7.4.0 fbjs-scripts: 3.0.1 => 3.0.1 i18n-js: 3.9.2 => 3.9.2 inherits: 2.0.1 isarray: 0.0.1 jest: ^29.2.1 => 29.7.0 jest-circus: 29 => 29.7.0 jest-environment-node: 29 => 29.7.0 jest-expo: ^49.0.0 => 49.0.0 jsdom: ^22.1.0 => 22.1.0 (20.0.3) jsdom-jscore-rn: ^0.1.8 => 0.1.8 libphonenumber-js: ^1.10.56 => 1.10.56 libphonenumber-js/build: undefined () libphonenumber-js/core: undefined () libphonenumber-js/max: undefined () libphonenumber-js/max/metadata: undefined () libphonenumber-js/min: undefined () libphonenumber-js/min/metadata: undefined () libphonenumber-js/mobile: undefined () libphonenumber-js/mobile/examples: undefined () libphonenumber-js/mobile/metadata: undefined () lodash: ^4.17.21 => 4.17.21 lodash.filter: ^4.6.0 => 4.6.0 lottie-react-native: ^5.1.6 => 5.1.6 metro-config: 0.76.8 => 0.76.8 metro-source-map: 0.75.1 => 0.75.1 (0.76.8) mobx: 6.6.0 => 6.6.0 mobx-react-lite: 3.4.0 => 3.4.0 mobx-state-tree: 5.1.5 => 5.1.5 mocha: ^10.2.0 => 10.2.0 nwmatcher: 1.4.3 patch-package: ^6.4.7 => 6.5.1 path-browserify: 0.0.0 postinstall-prepare: 1.0.1 => 1.0.1 prettier: 2.8.8 => 2.8.8 (3.2.4) query-string: ^7.0.1 => 7.1.3 (6.10.1) querystring: 0.2.0 react: 18.2.0 => 18.2.0 react-devtools-core: 4.24.7 => 4.24.7 (4.28.5) react-dom: 18.2.0 => 18.2.0 react-native: 0.72.6 => 0.72.6 react-native-bootsplash: ^5.0.2 => 5.2.2 react-native-compressor: ^1.8.23 => 1.8.23 react-native-device-info: ^10.12.0 => 10.12.0 react-native-dots-pagination: ^0.3.1 => 0.3.1 react-native-element-dropdown: ^2.9.0 => 2.10.1 react-native-fs: ^2.20.0 => 2.20.0 react-native-gesture-handler: ~2.12.0 => 2.12.1 react-native-get-random-values: ~1.9.0 => 1.9.0 react-native-mmkv: ^2.11.0 => 2.11.0 react-native-pager-view: 6.2.0 => 6.2.0 react-native-reanimated: ~3.3.0 => 3.3.0 react-native-receive-sharing-intent: ^2.0.0 => 2.0.0 react-native-render-html: ^6.3.4 => 6.3.4 react-native-safe-area-context: 4.6.3 => 4.6.3 react-native-screens: ~3.22.0 => 3.22.1 react-native-shimmer-placeholder: ^2.0.9 => 2.0.9 react-native-static-safe-area-insets: ^2.2.0 => 2.2.0 react-native-touchable-scale: ^2.2.0 => 2.2.0 react-native-url-polyfill: ^2.0.0 => 2.0.0 react-native-video: ^6.0.0-beta.4 => 6.0.0-beta.4 react-native-vision-camera: ^3.9.0 => 3.9.0 react-native-volume-manager: ^1.10.0 => 1.10.0 react-native-web: ~0.19.6 => 0.19.10 react-native-webview: 13.2.2 => 13.2.2 react-native-youtube-iframe: ^2.3.0 => 2.3.0 react-test-renderer: 18.2.0 => 18.2.0 reactotron-core-client: ^2.8.10 => 2.8.11 (2.8.10) reactotron-mst: 3.1.4 => 3.1.4 reactotron-react-js: ^3.3.7 => 3.3.9 reactotron-react-native: 5.0.3 => 5.0.3 regenerator-runtime: ^0.13.4 => 0.13.11 (0.14.1) sentry-expo: ~7.1.0 => 7.1.1 string_decoder: 0.10.31 ts-jest: 29 => 29.1.1 typescript: ^4.9.4 => 5.3.3 urlmaster: 0.2.15 uuid: ^9.0.1 => 9.0.1 (8.3.2, 3.4.0, 7.0.3) npmGlobalPackages: @aws-amplify/cli-internal: 12.10.1 @aws-amplify/cli: 12.10.1 @react-native-community/netinfo: 9.4.1 eas-cli: 7.2.0 expo-cli: 6.3.10 firebase-tools: 11.24.1 n: 9.1.0 node-gyp: 10.0.1 node: 20.6.0 npm: 10.3.0 pod-install: 0.1.39 react-native-spinkit: 1.5.1 ```

Describe the bug

When using Amplify Authentication with SMS verification, the user is created in AWS Cognito before they verify the six digit code.

This creates a major security flaw in the authentication flow. Someone could create a bot or script to create a million different accounts with phone numbers and just block all of them out without having to verify.

Expected behavior

A user should not be created within cognito until AFTER the user has been verified.

Reproduction steps

My authentication flow:

  1. User enters in first and last name -> next screen
  2. User enters in birthday -> next screen
  3. User enters in phone number -> next screen
  4. User enters in username and password Code is:
    const { isSignUpComplete, nextStep } = await signUp({ // THIS IS WHERE THE USER IS CREATED IN COGNITO AND SHOULD NOT BE
          username,
          password,
          options: {
            autoSignIn: {
              enabled: true,
            },
            userAttributes: {
              birthdate,
              given_name: firstName.trim(),
              family_name: lastName.trim(),
              phone_number: phoneNumber,
            },
          },
        })
    if (!isSignUpComplete && nextStep.signUpStep === "CONFIRM_SIGN_UP") {
       navigation.navigate("Verification")
    }
  5. User enters in 6 digit code on verification screen. (THIS IS WHERE THE USER SHOULD BE CREATED IN COGNITO AFTER THEY HAVE ENTERED IN THE CORRECT CODE)

Code Snippet

Refer to the above.

Log output

``` // Put your logs below this line ```

aws-exports.js

/ eslint-disable / // WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.

const awsmobile = { "aws_project_region": "us-east-1", "aws_appsync_graphqlEndpoint": "https://5tvnvesrgjfjdclupp5rzrh5gi.appsync-api.us-east-1.amazonaws.com/graphql", "aws_appsync_region": "us-east-1", "aws_appsync_authenticationType": "API_KEY", "aws_appsync_apiKey": REDACTED, "aws_cognito_identity_pool_id": REDACTED, "aws_cognito_region": "us-east-1", "aws_user_pools_id": "us-east-1_eDAImGHL9", "aws_user_pools_web_client_id": "4uj0jgf4p8m3au9h8u6g429111", "oauth": {}, "aws_cognito_username_attributes": [], "aws_cognito_social_providers": [], "aws_cognito_signup_attributes": [ "GIVEN_NAME", "FAMILY_NAME", "BIRTHDATE", "PHONE_NUMBER" ], "aws_cognito_mfa_configuration": "OFF", "aws_cognito_mfa_types": [], "aws_cognito_password_protection_settings": { "passwordPolicyMinLength": 8, "passwordPolicyCharacters": [ "REQUIRES_LOWERCASE", "REQUIRES_UPPERCASE", "REQUIRES_NUMBERS", "REQUIRES_SYMBOLS" ] }, "aws_cognito_verification_mechanisms": [ "PHONE_NUMBER" ], "aws_user_files_s3_bucket": "reelfeelmedia", "aws_user_files_s3_bucket_region": "us-east-1" };

export default awsmobile;

Manual configuration

No response

Additional configuration

No response

Mobile Device

iPhone 12 (physical)

Mobile Operating System

iOS 17.2.1

Mobile Browser

N/A

Mobile Browser Version

N/A

Additional information and screenshots

Screenshot 2024-02-19 at 3 32 40 PM

I created a random account with a random phone number and random name but didn't verify and now that phone number is blocked forever even though no one has verified it.

nadetastic commented 8 months ago

Hi @ChristopherGabba thank you for opening this issue. As you have pointed out, the workflow that you are experiencing is expected and is how Cognito creates users. However, note that although the user is created, they remain as unconfirmed until they either complete a verification flow, or is confirmed through an Administrator action [1]. Note that a user who is unconfirmed is unable to sign in until they are confirmed by by verifying themselves via email or phone.

One potential solution to address you concern of a phone number being blocked forever - you may be able to automate a process that periodically Deletes a user based on their confirmation status.

[1] https://docs.aws.amazon.com/cognito/latest/developerguide/signing-up-users-in-your-app.html

ChristopherGabba commented 8 months ago

@nadetastic Thanks as always for helping me out!

I see based off your link that this is the intended Cognito workflow, but I still feel like this needs to be addressed.

As a customer, I am paying for every verification SMS that is sent and every authenticated user. Someone could sit at their phone or tablet and cost me thousands of dollars in new authenticated sign ups and text message fees for dummy accounts.

The solution that you propose with an automated Lambda process (or some other means) also will cost me money as a customer as Lambda also has costs associated with it. If you have any guidance on how to approach this, or some literature, I am interested in it regardless, as I think I'm going to need this.

I will work on some code in the internal phone state that if a user has created more than say 2 phone numbers, it blocks the user somehow. I'll think about it.

Regardless, I believe this is a pretty large security and financial issue with the existing workflow. I do not think a user should be created officially until that have confirmed their status -- this solves all the problems except the possibility of someone costing me a lot in SMS codes, which I think I could put some client-side logic in to protect for. This workflow would also prevent me from having to build some custom new screen in my app in case a users "phone number is already taken", where they could send me a request to get it unblocked somehow. I'm worried that a customer will try to sign up with their phone number and it will already be taken by some bot or script.

One last comment is that sometimes users enter in their phone number with a missing or incorrect digit here or there. With this workflow, when they press sign up, it will send their incorrect phone number a verification code and create a new account for that incorrect phone number. Which means I may get one or two unconfirmed false phone numbers every now and then when a user enters in an incorrect number -- also costing me more money as a customer.

Another solution to this is to not charge customers (like myself) for "Unconfirmed" users, which I doubt AWS will consent to.

ChristopherGabba commented 8 months ago

@nadetastic Part of the issue with my setup I believe is that I'm using username as the main authentication method and I'm using phoneNumber as a sign in alias. This means that the user can sign in with username or phone (this works great).

Here is my aws-export.js:

const awsmobile = {
    ...
    "aws_cognito_username_attributes": [],
    "aws_cognito_social_providers": [],
    "aws_cognito_signup_attributes": [
        "GIVEN_NAME",
        "FAMILY_NAME",
        "BIRTHDATE",
        "PHONE_NUMBER"
    ],
    "aws_cognito_mfa_configuration": "OFF",
    "aws_cognito_mfa_types": [],
    "aws_cognito_password_protection_settings": {
        "passwordPolicyMinLength": 8,
        "passwordPolicyCharacters": [
            "REQUIRES_LOWERCASE",
            "REQUIRES_UPPERCASE",
            "REQUIRES_NUMBERS",
            "REQUIRES_SYMBOLS"
        ]
    },
    "aws_cognito_verification_mechanisms": [
        "PHONE_NUMBER"
    ],
   ...
};

How can I make it to where phone numbers also have to be unique?

Right now if I try to create an account, I can make 10 users have the same phone number but different usernames. I don't want this to be the case. How can I adjust my settings to force both to be unique?

nadetastic commented 8 months ago

@ChristopherGabba If you would like to have the phone numbers to be unique, I'd recommend making the phone number as the primary login method as that will force it to be unique for all your users.

Also to clarify, although user a and b can have the same phone number attribute, it should not cause issues as any messages sent will always be directed to the user that physically has the phone. But also note that if you do set phone number as the primary login method, then a user could sign up with another persons phone number however should never be able to be confirmed as they won't be able to receive the confirmation code sent to the phone number.

ChristopherGabba commented 8 months ago

@nadetastic Thanks @nadetastic I'll close this issue. This is how AWS Cognito is designed / intended.