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

Amplify V6 -- signin and confirmsignin cannot work separatly?

Open zcemycl opened this issue 1 year ago • 21 comments

Before opening, please confirm:

JavaScript Framework

Next.js

Amplify APIs

Authentication

Amplify Version

v6

Amplify Categories

auth

Backend

Other

Environment information

# Put output below this line
  System:
    OS: macOS 14.4
    CPU: (8) arm64 Apple M1
    Memory: 70.72 MB / 8.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.10.0 - ~/.nvm/versions/node/v20.10.0/bin/node
    npm: 10.2.3 - ~/.nvm/versions/node/v20.10.0/bin/npm
  Browsers:
    Chrome: 123.0.6312.59
    Safari: 17.4
  npmPackages:
    @ampproject/toolbox-optimizer:  undefined ()
    @aws-amplify/adapter-nextjs: ^1.0.21 => 1.0.21 
    @aws-amplify/adapter-nextjs/api:  undefined ()
    @aws-amplify/adapter-nextjs/data:  undefined ()
    @aws-sdk/client-cognito-identity: ^3.540.0 => 3.540.0 
    @aws-sdk/client-cognito-identity-provider: ^3.540.0 => 3.540.0 
    @babel/core:  undefined ()
    @babel/runtime:  7.22.5 
    @edge-runtime/cookies:  4.1.0 
    @edge-runtime/ponyfill:  2.4.2 
    @edge-runtime/primitives:  4.1.0 
    @hapi/accept:  undefined ()
    @mswjs/interceptors:  undefined ()
    @napi-rs/triples:  undefined ()
    @next/font:  undefined ()
    @next/react-dev-overlay:  undefined ()
    @opentelemetry/api:  undefined ()
    @types/node: ^20 => 20.11.30 
    @types/react: ^18 => 18.2.67 
    @types/react-dom: ^18 => 18.2.22 
    @vercel/nft:  undefined ()
    @vercel/og:  0.6.2 
    acorn:  undefined ()
    amphtml-validator:  undefined ()
    anser:  undefined ()
    arg:  undefined ()
    assert:  undefined ()
    async-retry:  undefined ()
    async-sema:  undefined ()
    autoprefixer: ^10.0.1 => 10.4.19 
    aws-amplify: ^6.0.21 => 6.0.21 
    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 ()
    babel-packages:  undefined ()
    browserify-zlib:  undefined ()
    browserslist:  undefined ()
    buffer:  undefined ()
    bytes:  undefined ()
    ci-info:  undefined ()
    cli-select:  undefined ()
    client-only:  0.0.1 
    comment-json:  undefined ()
    compression:  undefined ()
    conf:  undefined ()
    constants-browserify:  undefined ()
    content-disposition:  undefined ()
    content-type:  undefined ()
    cookie:  undefined ()
    cross-spawn:  undefined ()
    crypto-browserify:  undefined ()
    css.escape:  undefined ()
    data-uri-to-buffer:  undefined ()
    debug:  undefined ()
    devalue:  undefined ()
    domain-browser:  undefined ()
    dotenv: ^16.4.5 => 16.4.5 
    edge-runtime:  undefined ()
    eslint: ^8.57.0 => 8.57.0 
    eslint-config-next: 14.1.4 => 14.1.4 
    eslint-config-prettier: ^9.1.0 => 9.1.0 
    events:  undefined ()
    find-cache-dir:  undefined ()
    find-up:  undefined ()
    fresh:  undefined ()
    get-orientation:  undefined ()
    glob:  undefined ()
    gzip-size:  undefined ()
    http-proxy:  undefined ()
    http-proxy-agent:  undefined ()
    https-browserify:  undefined ()
    https-proxy-agent:  undefined ()
    icss-utils:  undefined ()
    ignore-loader:  undefined ()
    image-size:  undefined ()
    is-animated:  undefined ()
    is-docker:  undefined ()
    is-wsl:  undefined ()
    jest-worker:  undefined ()
    json5:  undefined ()
    jsonwebtoken:  undefined ()
    loader-runner:  undefined ()
    loader-utils:  undefined ()
    lodash.curry:  undefined ()
    lru-cache:  undefined ()
    micromatch:  undefined ()
    mini-css-extract-plugin:  undefined ()
    nanoid:  undefined ()
    native-url:  undefined ()
    neo-async:  undefined ()
    next: 14.1.4 => 14.1.4 
    next-auth: ^4.24.7 => 4.24.7 
    next-themes: ^0.3.0 => 0.3.0 
    node-fetch:  undefined ()
    node-html-parser:  undefined ()
    ora:  undefined ()
    os-browserify:  undefined ()
    p-limit:  undefined ()
    path-browserify:  undefined ()
    platform:  undefined ()
    postcss: ^8 => 8.4.38 (8.4.31)
    postcss-flexbugs-fixes:  undefined ()
    postcss-modules-extract-imports:  undefined ()
    postcss-modules-local-by-default:  undefined ()
    postcss-modules-scope:  undefined ()
    postcss-modules-values:  undefined ()
    postcss-preset-env:  undefined ()
    postcss-safe-parser:  undefined ()
    postcss-scss:  undefined ()
    postcss-value-parser:  undefined ()
    prettier: ^3.2.5 => 3.2.5 
    process:  undefined ()
    punycode:  undefined ()
    querystring-es3:  undefined ()
    raw-body:  undefined ()
    react: ^18 => 18.2.0 
    react-builtin:  undefined ()
    react-dom: ^18 => 18.2.0 
    react-dom-builtin:  undefined ()
    react-dom-experimental-builtin:  undefined ()
    react-experimental-builtin:  undefined ()
    react-is:  18.2.0 
    react-refresh:  0.12.0 
    react-server-dom-turbopack-builtin:  undefined ()
    react-server-dom-turbopack-experimental-builtin:  undefined ()
    react-server-dom-webpack-builtin:  undefined ()
    react-server-dom-webpack-experimental-builtin:  undefined ()
    regenerator-runtime:  0.13.4 
    sass-loader:  undefined ()
    scheduler-builtin:  undefined ()
    scheduler-experimental-builtin:  undefined ()
    schema-utils:  undefined ()
    semver:  undefined ()
    send:  undefined ()
    server-only:  0.0.1 
    setimmediate:  undefined ()
    sharp: ^0.33.2 => 0.33.2 
    shell-quote:  undefined ()
    source-map:  undefined ()
    stacktrace-parser:  undefined ()
    stream-browserify:  undefined ()
    stream-http:  undefined ()
    string-hash:  undefined ()
    string_decoder:  undefined ()
    strip-ansi:  undefined ()
    superstruct:  undefined ()
    tailwindcss: ^3.3.0 => 3.4.1 
    tar:  undefined ()
    terser:  undefined ()
    text-table:  undefined ()
    timers-browserify:  undefined ()
    ts-node: ^10.9.2 => 10.9.2 
    tty-browserify:  undefined ()
    typescript: ^5 => 5.4.3 
    ua-parser-js:  undefined ()
    unistore:  undefined ()
    util:  undefined ()
    vm-browserify:  undefined ()
    watchpack:  undefined ()
    web-vitals:  undefined ()
    webpack:  undefined ()
    webpack-sources:  undefined ()
    ws:  undefined ()
    zod:  undefined ()
  npmGlobalPackages:
    @aws-amplify/cli: 12.10.1
    corepack: 0.22.0
    npm: 10.2.3

Describe the bug

I have the following workflow,

  1. Sign in with custom challenge. The cognito will reply with a magic link.
  2. Click on the magic link will open up a new tab to send confirmsignin.

That will not work because amplify v6 needs signin and confirmsignin command from 'aws-amplify/auth' to be placed in the same function so that the next step in signin can continue.

However, from "@aws-sdk/client-cognito-identity" package, InitiateAuthCommand and RespondToAuthChallengeCommand can be separated in callbacks by session id.

Expected behavior

Allow signIn and confirmSignIn to be separated.

Reproduction steps

  1. Install aws-amplify v6
  2. Create two scripts. One script just to use signIn, another just to use confirmSignIn.

Code Snippet

// Put your code below this line.
  async function amplifySignIn(email: string) {
    const resp = await login({
      username: email,
      options: { authFlowType: "CUSTOM_WITHOUT_SRP" },
    });
    return resp;
  }

  async function amplifyConfirmSignIn(email: string, code: string) {
    const resp = await confirmSignIn({
      challengeResponse: code,
    });
  }

Log output

// Put your logs below this line
confirmSignIn.mjs:58 Uncaught (in promise) SignInException: 
			An error occurred during the sign in process. 
			
			This most likely occurred due to:
			1. signIn was not called before confirmSignIn.
			2. signIn threw an exception.
			3. page was refreshed during the sign in flow.

aws-exports.js

No response

Manual configuration

No response

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

zcemycl avatar Mar 25 '24 22:03 zcemycl

Hello, @zcemycl and thanks for opening this issue. You are correct that confirmSignIn() currently needs to be called alongside signIn() due to the session information and challengeResponse being required as part of the confirmation flow.

Are you able to provide more context around what the use case is here or why you're needing to call these separately in the authentication flow? Thanks!

cwomack avatar Mar 26 '24 12:03 cwomack

@cwomack for example, this one from aws tutorial https://aws.amazon.com/blogs/mobile/implementing-passwordless-email-authentication-with-amazon-cognito/ .

  • You login the website by providing email that is in the user pool.
  • the custom auth flow allows that user by issuing an auth challenge which is sending the user email a magic link.
  • If the user clicks on the magic link, it supposes to send a confirm auth challenge to custom auth flow to verify the user identity.
  • BUT since clicking the magic link or copying magic link to the browser will lead refreshing the page, the original signin function is discontinued.

I can replicate that tutorial with "@aws-sdk/client-cognito-identity-provider" but "aws-amplify" is not working.

zcemycl avatar Mar 26 '24 13:03 zcemycl

Related to #10469.

@zcemycl can you review that issue I linked above to see if it would encompass what you're asking for in this issue?

cwomack avatar Mar 26 '24 18:03 cwomack

@cwomack I can confirm it is similar, except that issue is from v5 amplify.sendCustomChallengeAnswer . But v5 can integrate nicer with amazon-cognito-identity-js (v2) to solve that issue.

My current issue is based on v6 and i can't see any examples of using amplify v6 and @aws-sdk/client-cognito-identity-provider v3 together.

zcemycl avatar Mar 26 '24 19:03 zcemycl

@zcemycl, thanks for the quick response! I've got this marked as a feature request and will review this internally with the work that is currently underway with the other issue. This may be considered a duplicate if that's the case, but we'll capture the scope (and of course v6 aspect) of what's in this issue if we consider them duplicates.

cwomack avatar Mar 26 '24 19:03 cwomack

Following up to say we'll keep both issues open and separate for now, because of the extra context here regarding the new/cross tab functionality described.

cwomack avatar Mar 27 '24 21:03 cwomack

We have the exact same use case, where we send a magic link to our users to login without a password. With v5, we're able to serialise the user/session into local storage, and hydrate it once the user clicks on the link in the email, thanks to interop with amazon-cognito-identity-js.

Having this part of amplify auth would be extremely awesome, or else have a way we can implement it on our side.

matmeylan avatar May 28 '24 12:05 matmeylan

@zcemycl @mmeylan I have magic link login working in Amplify v6, using the CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE flow and then call confirmSignIn({ challengeResponse: challenge } which will log me in.

For this to work, I have had to setup multiple hooks in Cognito to log you in when you do the custom challenge. Is this what you are looking for?

mattiLeBlanc avatar Jul 13 '24 08:07 mattiLeBlanc

@mattiLeBlanc Does your implementation work when the magic link is opened in a new tab or after a page reload ? Here's my exact flow:

  1. User opens sign-in page, enters email (amplify signIn called)
  2. Email is sent to user with sign-in challenge in the link
  3. User clicks on link in email, new page is opened with challenge in the query params (amplify confirmSignIn({challengeResponse: challenge}) called) <-- does not work because session created by 1. does not exist.

As far as I could tell from by tests on v6, the session is stored in memory by amplify, but maybe I am missing something.

@zcemycl @mmeylan I have magic link login working in Amplify v6, using the CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE flow and then call confirmSignIn({ challengeResponse: challenge } which will log me in.

For this to work, I have had to setup multiple hooks in Cognito to log you in when you do the custom challenge. Is this what you are looking for?

matmeylan avatar Jul 18 '24 07:07 matmeylan

@mmeylan Can you please let us know what changes have you made to work it. like you implemented hooks.

shubhamkahndelwal avatar Aug 06 '24 08:08 shubhamkahndelwal

@mattiLeBlanc @mmeylan Any update on your solution? We have a very similar issue.

kyletsuitsme avatar Aug 19 '24 16:08 kyletsuitsme

@mmeylan It can work on both, but if you open in the same tab or window (with localstorage), you have to programmatically logout before processing the hook, at least that is what I do.

So what I do:

  • I create a new cognito user, create a token, put that token as a custom:authChallenge attr in the cognito user
  • I construct the link with a websafe version of the token
  • user opens link, goes to website, token gets validated in backend and decoded and login:
 ....

 return this.programmaticLogout({ clearState: true }).pipe(
      switchMap(() => this.signIn({ username })
        .pipe(
          switchMap(res => {
            if (res === 'CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE') {
              return from(confirmSignIn({ challengeResponse: challenge }))
                .pipe(
                  map(res => res.nextStep.signInStep)
                );
            } else {
              return of(res);
            }
          }),
          switchMap(status => {
            if (status === 'DONE') {
              return this.getAuthSession()
            }
            return undefined;
          }),
....

I had to setup plenty of hooks in the Auth stack based on info from : -https://theburningmonk.com/2023/03/implementing-magic-links-with-amazon-cognito-a-step-by-step-guide/ -https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-define-auth-challenge.html#aws-lambda-triggers-define-auth-challenge-example

I am a bit busy but if you are still having issues, I got try setting up a test stack in gtihub that works, you will have to deploy it your self of course, but you could test it.

mattiLeBlanc avatar Aug 20 '24 00:08 mattiLeBlanc

We have the same issue with Cognito's new passwordless feature. We want to send an email with a magic link (we use the CustomMessage Lambda function to compose and send it - including the code itself). The user can click the link in the email. Hence, we can't call signIn again as it would sent another magic link.

ronnyroeller avatar Dec 04 '24 08:12 ronnyroeller

Hey @ronnyroeller, thanks for chiming in and letting us know you're also experiencing friction here. We've got this marked as a feature request and are reviewing internally to come up with a solution.

jjarvisp avatar Dec 04 '24 17:12 jjarvisp

We are experiencing the same issue, and according to the Passwordless sign-in with one-time passwords documentation, we have encountered the following problems:

Issues

  1. Missing Session in Auth.signIn Response:

    • The Session attribute is not included in the response of Auth.signIn. However, the Session is required to complete the authentication process using Auth.confirmSignIn.
  2. Session Needed in the Email Template:

    • Since Session is necessary for Auth.confirmSignIn, it is also required in the email template to include it in the query string of the magic link (e.g., https://login.example.com/signin?code=12345678&session=<Session>).
  3. Set Session Timeout:

    • The Session timeout is currently less than 5 minutes, which is not suitable in some cases. If we call the Auth.confirmSignIn request more than 5 minutes after the Auth.signIn request, it throws the error: Invalid session for the user, session is expired.
    • There should be an option to configure the Session timeout to suit different use cases.

eacet avatar Dec 05 '24 05:12 eacet

Hey @eacet, thanks for adding additional momentum here. We are currently working towards implementing some changes that should make the flow you are describing possible. We'll follow up here when we have more information to share.

In the meantime, feel free to checkout some of the other passwordless options to see if any fit your use case.

jjarvisp avatar Dec 05 '24 19:12 jjarvisp

We're dealing with pretty similar issue with Amplify v6 too. Looking forward to the solution to this

oduk-rbi avatar Jan 10 '25 14:01 oduk-rbi

Does the Amplify team have a status update on supporting this flow?

brandonhall avatar Mar 13 '25 14:03 brandonhall

Hi @brandonhall ,

We are still evaluating the security implication of this feature, thank you for your patience.

yuhengshs avatar Mar 13 '25 19:03 yuhengshs

My business owners think I'm junk because I can't implement a "simple" magic link with Cognito that every other company can.

If I call fetchAuthSession() and store the sessionToken to retrieve it for use in confirmSingIn(), will that work? @eacet , I tried including it as a URL query parameter manually when calling the page that does confirmSignIn(), but this did not yield any results. Perhaps I should encodeUrlComponent(sessionToken)? Did you find any other methods to circumvent this limitation?

It looks like the fact that session information doesn't transfer from one tab to another is what ruins the flow.

malikalimoekhamedov avatar Mar 26 '25 18:03 malikalimoekhamedov

Hi @malikalimoekhamedov @zcemycl

You may find the sample app for passwordless sign-in helpful implementing a magic link flow.

Regarding the question,

If I call fetchAuthSession() and store the sessionToken to retrieve it for use in confirmSingIn(), will that work?

This will not work unfortunately. The session token used by confirmSignIn() / respondToAuthChallenge can not be set from external.

AllanZhengYP avatar Mar 26 '25 20:03 AllanZhengYP