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

The autoSignIn flow has not started, or has been cancelled/completed #13838

Closed al-mcnichol closed 1 month ago

al-mcnichol commented 1 month ago

Before opening, please confirm:

JavaScript Framework

Web Components

Amplify APIs

Authentication

Amplify Version

v6

Amplify Categories

auth

Backend

None

Environment information

``` # Put output below this line System: OS: Linux 5.15 Ubuntu 22.04.2 LTS 22.04.2 LTS (Jammy Jellyfish) CPU: (16) x64 11th Gen Intel(R) Core(TM) i9-11950H @ 2.60GHz Memory: 9.33 GB / 15.23 GB Container: Yes Shell: 5.1.16 - /bin/bash Binaries: Node: 22.8.0 - ~/.nvm/versions/node/v22.8.0/bin/node npm: 10.8.2 - ~/.nvm/versions/node/v22.8.0/bin/npm npmPackages: @optimizely/optimizely-sdk: ^5.3.4 => 5.3.4 @stencil/core: ^4.19.2 => 4.21.0 @stencil/core/cli: 4.21.0 @stencil/core/compiler: 4.21.0 @stencil/core/dev-server: 4.21.0 @stencil/core/dev-server/client: 4.21.0 @stencil/core/internal: 4.21.0 @stencil/core/internal/app-data: 4.21.0 @stencil/core/internal/client: 4.21.0 @stencil/core/internal/hydrate: 4.21.0 @stencil/core/internal/testing: 4.21.0 @stencil/core/mock-doc: 4.21.0 @stencil/core/screenshot: 4.21.0 @stencil/core/sys/node: 4.21.0 @stencil/core/testing: 4.21.0 @stencil/sass: ^2.0.3 => 2.0.4 @stencil/store: ^2.0.3 => 2.0.16 @types/jest: ^28.1.1 => 28.1.8 @types/node: ^20.10.4 => 20.16.5 autoprefixer: ^10.4.13 => 10.4.20 aws-amplify: ^6.6.0 => 6.6.0 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/data: undefined () aws-amplify/data/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 () http-proxy: ^1.18.1 => 1.18.1 http-server: ^14.1.1 => 14.1.1 husky: ^8.0.3 => 8.0.3 jest: ^29.7.0 => 29.7.0 jest-cli: ^29.7.0 => 29.7.0 jwt-decode: ^3.1.2 => 3.1.2 mitt: ^3.0.0 => 3.0.1 (3.0.0) npm-check: ^6.0.1 => 6.0.1 puppeteer: ^20.3.0 => 20.9.0 rollup-plugin-node-polyfills: ^0.2.1 => 0.2.1 stencil-tailwind-plugin: ^1.7.0 => 1.8.0 tailwindcss: ^3.4.3 => 3.4.11 typescript: ^5.3.3 => 5.6.2 uuid: ^9.0.1 => 9.0.1 npmGlobalPackages: @aws-amplify/cli: 12.12.6 corepack: 0.29.3 npm: 10.8.2 ```

Describe the bug

Calling autoSignIn() after confirmSignUp() when signUpStep is "COMPLETE_AUTO_SIGN_IN" is throwing error "The autoSignIn flow has not started, or has been cancelled/completed".

Expected behavior

I expected this to set local storage with the current signed in user tokens, etc

Reproduction steps

This is being done in a single session where a modal is presented to user to sign-up (autoSignIn: true), email sent with the code, then code validated.

return await Auth.confirmSignUp({ username, confirmationCode: code })
    .then( async ({ isSignUpComplete, userId, nextStep}) => {
      console.log('confirmSignUp::isSignUpComplete | ', isSignUpComplete);
      console.log('confirmSignUp::userId | ', userId);
      console.log('confirmSignUp::nextStep | ', nextStep);
      if (isSignUpComplete && nextStep.signUpStep === 'COMPLETE_AUTO_SIGN_IN') {
        const { isSignedIn } = await Auth.autoSignIn();
        if (isSignedIn){
          await setCurrentAuthProvider('cognito');
          globalThis.dispatchEvent(new Event('auth.signin'));
          emit('auth.signin');
        }
      }
      return actionReturnStatus.success(userId);

    })

console.log output:

confirmSignUp::isSignUpComplete | true confirmSignUp::userId | undefined confirmSignUp::nextStep | {signUpStep: 'COMPLETE_AUTO_SIGN_IN'}

image

Code Snippet

// Put your code below this line.

Log output

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

aws-exports.js

No response

Manual configuration


const commonOauthConfig: OAuthConfig = {
  domain: 'xxxxx.com',
  providers: ["Google", 'Apple', 'Facebook'],
  redirectSignIn: [origin + federatedRedirectPath],
  redirectSignOut: [origin + '/account/sign-in'],
  responseType: 'code',
  scopes: ['aws.cognito.signin.user.admin', 'openid'],
};

const amplifyConfig: ResourcesConfig = isEnvironmentPROD ? {
  Auth: {
    Cognito: {
      userPoolId: 'us-east-1_xxxxxx',
      userPoolClientId: clientId,
      signUpVerificationMethod: 'code',
      loginWith: {
        oauth: {
          ...commonOauthConfig,
        },
        username: true,
      },
    }
  }
} ```

### Additional configuration

_No response_

### Mobile Device

_No response_

### Mobile Operating System

_No response_

### Mobile Browser

_No response_

### Mobile Browser Version

_No response_

### Additional information and screenshots

_No response_
al-mcnichol commented 1 month ago

btw, "Auth" is my own convention, where I export a custom Auth object to expose the APIs from 'aws-amplify/auth' not to be confused with amplify versions below v6. This was to localize options needed.

export const Auth = {
  signIn: (params) =>  signIn({ ...params, options}),
  signInCustomWithoutSRP: (params) =>  signIn({ ...params, options: { ...options, authFlowType: 'CUSTOM_WITHOUT_SRP'} }),
  confirmSignIn,
  signUp: (params) => signUp({ ...params, options: { ...options, ...params.options }}),
  confirmSignUp: (params) => confirmSignUp({ ...params, options}),
  confirmResetPassword: (params) => confirmResetPassword({ ...params, options}),
  updateUserAttributes: (params) => updateUserAttributes({ ...params, options}),
  resetPassword: (params) => resetPassword({ ...params, options}),
  updatePassword,
  fetchUserAttributes,
  sendUserAttributeVerificationCode,
  getCurrentUser,
  fetchAuthSession,
  resendSignUpCode,
  signOut,
  decodeJWT,
  autoSignIn,
};
cwomack commented 1 month ago

Hello, @al-mcnichol 👋. Are you able to provide additional code tied to your auth flow that precedes your confirmSignUp() code provided above? And can you give more details on where the "The autoSignIn flow has not started, or has been cancelled/completed" error is coming from more specifically via a network request or screenshot?

Any additional code related to your custom Auth setup may be helpful to share as well as possibly a minimal sample repo for easier reproduction. Thanks!

al-mcnichol commented 1 month ago

@cwomack - Here is our custom UI, which ultimately calls Amplify signUp API

image

SignUp API:

await Auth.signUp({
    username: 'fake.email@fake.com',
    password: 'Fake@Passw0rd',
    attributes: {
      given_name: 'Fake First',
      family_name: 'Fake Last',
    },
    autoSignIn: {
      // enables auto sign in after user is confirmed
      enabled: true,
    },
  })

ConfirmSignUp & autoSignIn APIs:

await Auth.confirmSignUp({ username, confirmationCode: code })
    .then( async ({ isSignUpComplete, userId, nextStep}) => {

      let isSignedIn;

      console.log('confirmSignUp::isSignUpComplete | ', isSignUpComplete);
      console.log('confirmSignUp::userId | ', userId);
      console.log('confirmSignUp::nextStep | ', nextStep);

      // If autoSignIn is enabled then call autoSignIn API only if signUpStep === 'COMPLETE_AUTO_SIGN_IN
      if (isSignUpComplete && nextStep.signUpStep === 'COMPLETE_AUTO_SIGN_IN') {
        // Auth.autoSignIn() may fail with msg::"The autoSignIn flow has not started, or has been cancelled/completed"
        isSignedIn = await Auth.autoSignIn().catch(() => { /*noop*/ });
        console.log('confirmSignUp::autoSignIn | ', isSignedIn)
        if (isSignedIn){
          await setCurrentAuthProvider('cognito');
          globalThis.dispatchEvent(new Event('auth.signin'));
          emit('auth.signin');
          return actionReturnStatus.success(userId);
        }
      }

      // If autoSignIn is enabled but Auth.autoSignIn() failed then, as a fallback, display
      // the sign-in screen with success alert to allow the user to manually sign-in. Only show
      // signIn screen if signUp was completed successfully, otherwise assume catastrophic error.
      if (isSignUpComplete && !isSignedIn) {
        storeAPI.set('screen', ScreenConstants.SIGN_IN);
        storeAPI.set('alert', {
          msg: 'Success! Please sign in.',
          type: 'success',
        });
        return actionReturnStatus.success(userId);
      }

      return Promise.reject({ name:'CustomException::SignUpFailedMiserably', message: errMsg });

    })
cwomack commented 1 month ago

@al-mcnichol, thank you for the additional code and context. It looks like the issue here could be tied to the fact that you're wrapping the autoSignIn() API, which will break the intended behavior at runtime because this API's implementation may change depending on the nextStep property (which is swamped at runtime). Can you test and see if removing your Auth wrapper object and calling the autoSignIn() API directly resolves the issue?

Also, is there a reason why you're creating the wrapper for the Auth API's? Not only will it break the intended behavior of the autoSignIn API, but it eliminates the tree-shaking advantages that Amplify does for you.

al-mcnichol commented 1 month ago

@cwomack Thanks so much for pointing that out. I used the API directly and it worked like a charm. The wrapper was to minimize repeat code for additional options needed but obviously not the right way to go.