Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot authorize with amplify aws and NextJS 14 app router. #13956

Closed
3 tasks done
cong-tri opened this issue Oct 25, 2024 · 6 comments
Closed
3 tasks done

Cannot authorize with amplify aws and NextJS 14 app router. #13956

cong-tri opened this issue Oct 25, 2024 · 6 comments
Assignees
Labels
Auth Related to Auth components/category Next.js question General question

Comments

@cong-tri
Copy link

Before opening, please confirm:

JavaScript Framework

React, Next.js

Amplify APIs

Authentication, Storage

Amplify Version

v6

Amplify Categories

auth, storage, hosting

Backend

None

Environment information

System:
OS: Windows 10 10.0.19045
CPU: (12) x64 Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
Memory: 19.96 GB / 31.82 GB
Binaries:
Node: 20.15.0 - C:\Program Files\nodejs\node.EXE
npm: 10.8.2 - C:\Program Files\nodejs\npm.CMD
Browsers:
Internet Explorer: 11.0.19041.1566
npmPackages:
@ampproject/toolbox-optimizer: undefined ()
@ant-design/icons: ^5.3.7 => 5.3.7
@ant-design/nextjs-registry: ^1.0.0 => 1.0.0
@aws-amplify/adapter-nextjs: ^1.2.11 => 1.2.11
@aws-amplify/adapter-nextjs/api: undefined ()
@aws-amplify/adapter-nextjs/data: undefined ()
@babel/core: undefined ()
@babel/runtime: 7.22.5
@edge-runtime/cookies: 4.1.1
@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 ()
@opentelemetry/api: undefined ()
@tanstack/query-codemods: undefined ()
@tanstack/react-query: ^5.50.1 => 5.51.1
@types/node: ^20 => 20.14.2
@types/react: ^18 => 18.3.3
@types/react-dom: ^18 => 18.3.0
@vercel/nft: undefined ()
@vercel/og: 0.6.2
acorn: undefined ()
amphtml-validator: undefined ()
anser: undefined ()
antd: ^5.18.1 => 5.18.1
arg: undefined ()
assert: undefined ()
async-retry: undefined ()
async-sema: undefined ()
aws-amplify: ^6.4.4 => 6.4.4
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
commander: undefined ()
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 ()
edge-runtime: undefined ()
eslint: ^8 => 8.57.0
eslint-config-next: 14.2.4 => 14.2.4
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 ()
mini-css-extract-plugin: undefined ()
moment: ^2.30.1 => 2.30.1
nanoid: undefined ()
native-url: undefined ()
neo-async: undefined ()
next: 14.2.4 => 14.2.4
node-fetch: undefined ()
node-html-parser: undefined ()
ora: undefined ()
os-browserify: undefined ()
p-limit: undefined ()
path-browserify: undefined ()
picomatch: 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 ()
process: undefined ()
punycode: undefined ()
querystring-es3: undefined ()
raw-body: undefined ()
react: ^18 => 18.3.1
react-builtin: undefined ()
react-dom: ^18 => 18.3.1
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 ()
recharts: ^2.12.7 => 2.12.7
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 ()
shell-quote: undefined ()
socket.io: ^4.7.5 => 4.7.5
socket.io-client: ^4.7.5 => 4.7.5
source-map: undefined ()
source-map08: undefined ()
stacktrace-parser: undefined ()
stream-browserify: undefined ()
stream-http: undefined ()
string-hash: undefined ()
string_decoder: undefined ()
strip-ansi: undefined ()
superstruct: undefined ()
tailwindcss: ^3.4.1 => 3.4.4
tar: undefined ()
terser: undefined ()
text-table: undefined ()
timers-browserify: undefined ()
tty-browserify: undefined ()
typescript: ^5 => 5.4.5
typescript-cookie: ^1.0.6 => 1.0.6
ua-parser-js: undefined ()
unistore: undefined ()
util: undefined ()
uuid: ^10.0.0 => 10.0.0 (9.0.1)
vm-browserify: undefined ()
watchpack: undefined ()
web-vitals: undefined ()
webpack: undefined ()
webpack-sources: undefined ()
ws: undefined ()
zod: undefined ()
npmGlobalPackages:
@aws-amplify/cli: 12.12.3
npm: 10.8.2
typescript: 5.5.4

Describe the bug

I have a project using nextjs 14 app router and amplify aws. I cannot handle login when i have called signIn and after called confirmSignIn to entered the challengeResponse OTP code to authorized. But cannot work for me, it just return error "Unable to get user session following successful sign-in.".
I have set up configure Amplify at client side with ssr = true and server side: runWithAmplifyServerContext with createServerRunner.
I don't know where the bug at, so i think may be at set up domain for cookie or something else can be missing.

Expected behavior

I want to code can work because i have this project for a long times
Sorry about the bug because i have studied amplify aws just a little.
Please help me.

Reproduction steps

  1. npm install aws-amplify , aws-amplify/adapter-nextjs
  2. configure amplify in nextjs:
  • client side with ssr = true
  • server side with runWithAmplifyServerContext
  1. call signIn and confirmSignIn in component sign-in-form

Code Snippet

// client side file config:
"use client";

import { Amplify } from "aws-amplify";
import { cognitoUserPoolsTokenProvider } from "aws-amplify/auth/cognito";
import { CookieStorage } from "aws-amplify/utils";
import { getAmplifyConfig } from "../../amplify-config";

const config = getAmplifyConfig();

Amplify.configure(config, {
// ssr: true,
});

cognitoUserPoolsTokenProvider.setKeyValueStorage(
new CookieStorage({
domain: http://${process.env.NEXT_PUBLIC_DOMAIN_NOT_SECURE},
path: ${process.env.NEXT_PUBLIC_BASE_PATH},
secure: false,
// sameSite: "strict",
})
);

export default function ConfigureAmplifyClientSide() {
return null;
}
I have called this component at the top children in layout.tsx

// server side file config :
import { createServerRunner } from "@aws-amplify/adapter-nextjs";
import { getAmplifyConfig } from "../../amplify-config";

const config = getAmplifyConfig();

export const { runWithAmplifyServerContext } = createServerRunner({
config: config,
});

// sign in component:
/** @Format */

"use client";

import React, { useEffect, useState } from "react";

import { useRouter } from "next/navigation";

import { App, Button, Form, Input, Modal } from "antd";
import Title from "antd/es/typography/Title";
import Typography from "antd/es/typography/Typography";

import {
AuthError,
SignInOutput,
confirmSignIn,
signIn,
} from "aws-amplify/auth";

import { v4 as uuidv4 } from "uuid";

type Account = {
username: string;
password: string;
};

function SignInForm() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isModal, setIsModal] = useState(false);
const [valid, setValid] = useState(false);
const [uuid, setUUID] = useState();
const [account, setAccount] = useState();
const [timeLeft, setTimeLeft] = useState(30);
const [output, setOutPut] = useState();

const { message } = App.useApp();

const [form] = Form.useForm();

const router = useRouter();

const errorException = "[DUPLICATED_DEVICE]"; // a handle error custom

useEffect(() => {
if (isModalOpen !== true) return;

if (timeLeft === 0) return;
const timerId = setInterval(() => {
  setTimeLeft(timeLeft - 1);
}, 1000);

return () => clearInterval(timerId);

}, [timeLeft, isModalOpen]);

const onFinish = async ({
username,
password,
}: {
username: string;
password: string;
}) => {
const myuuid = uuidv4();

setUUID(myuuid);

setAccount({ username, password });

try {
  const response = await signIn({
    username,
    password,
    options: {
      clientMetadata: {
        uuid: myuuid,
      },
    },
  });
  console.log(response);

  if (response.isSignedIn === false) setIsModalOpen(true);
  else {
    message.success("Login Successfully", 2, () => {
      router.refresh();
      router.push("/public-portal/user");
    });
  }
} catch (error) {
  handleThrowErrorMessage(error, errorException);
}

};

const handleReturnOutputSignin = async () => {
try {
const output = await signIn({
username: account?.username ?? "",
password: account?.password,
options: {
clientMetadata: {
uuid: uuid ?? "",
mfaMethod: "EMAIL",
},
},
});
return output;
} catch (error) {
handleThrowErrorMessage(error);
}
};

const handleContinueToSignin = async () => {
try {
const output = await handleReturnOutputSignin();
console.log("output >>", output);

  setOutPut(output);
  if (output?.nextStep.signInStep === "CONFIRM_SIGN_IN_WITH_SMS_CODE")
    setIsModalOpen(true);
} catch (error) {
  handleThrowErrorMessage(error);
}

};

const handleSignInNextSteps = async ({ otpCode }: { otpCode: string }) => {
try {
if (!output) await handleReturnOutputSignin();

  if (
    output?.isSignedIn === false &&
    output?.nextStep.signInStep === "CONFIRM_SIGN_IN_WITH_SMS_CODE"
  ) {
    await confirmSignIn({ challengeResponse: otpCode, options: {
      userAttributes: {}
    } });
    setValid(true);

    message.success("Login Successfully", 2, () => {
      router.refresh();
      router.push("/user");
    });
  }
} catch (error) {
  handleThrowErrorMessage(error);
}

};

const handleResendOTPCode = async () => {
try {
await handleReturnOutputSignin();
setTimeLeft(30);
} catch (error) {
handleThrowErrorMessage(error);
}
};

const handleThrowErrorMessage = (error: any, errorKey: string = "") => {
console.error(error);

if (error instanceof AuthError) {
  if (errorKey != errorException) {
    message.error(error.message, 5);
  }
}

if (error.message.includes(errorKey)) {
  setIsModal(true);
}

};

return (
<>
<Form
form={form}
name="basic"
onFinish={onFinish}
autoComplete="off"
style={{ width: 800 }}
>
<Title level={3}>Username:</Title>
<Form.Item
name="username"
style={{ width: "100%" }}
rules={[{ required: true, message: "Please input your username!" }]}
initialValue={"+84326034561"}
>

</Form.Item>

    <Title level={3}>Password:</Title>
    <Form.Item<Account>
      name="password"
      rules={[{ required: true, message: "Please input your password!" }]}
      initialValue={"Tri@2024"}
    >
      <Input.Password placeholder="Your password" size="large" />
    </Form.Item>

    <Form.Item>
      <Button htmlType="submit" type="primary" size="large">
        Submit
      </Button>
    </Form.Item>
  </Form>

  <Modal
    title={<Title level={3}>Sign In Failed</Title>}
    centered
    width={300}
    open={isModal}
    onOk={() => setIsModal(true)}
    onCancel={() => setIsModal(false)}
    footer={
      <>
        <Button
          htmlType="button"
          type="primary"
          danger
          onClick={() => setIsModal(false)}
        >
          Close
        </Button>
        <Button
          htmlType="button"
          type="primary"
          onClick={() => handleContinueToSignin()}
        >
          CONTINUE
        </Button>
      </>
    }
  >
    <Typography>
      You have already logged in on another device. Please sign out of the
      other device or CONTINUE
    </Typography>
  </Modal>

  <Modal
    title={<Title type="secondary">Verification</Title>}
    centered
    width={350}
    open={isModalOpen}
    onOk={() => setIsModalOpen(true)}
    onCancel={() => {
      if (!valid) {
        message.error("Please submit the OTP code to close");
        return;
      }
      setIsModalOpen(false);
    }}
    footer={
      <>
        <Button
          htmlType="button"
          type="primary"
          danger
          disabled={!valid}
          onClick={() => setIsModalOpen(false)}
        >
          Close
        </Button>
      </>
    }
  >
    <Form
      form={form}
      name="otp"
      onFinish={handleSignInNextSteps}
      autoComplete="off"
      style={{ width: "100%" }}
    >
      <Title level={4}>
        Please, enter the verification code that has been sent to your email
      </Title>
      <Form.Item
        name="otpCode"
        hasFeedback
        validateStatus="success"
        rules={[
          {
            required: true,
            message: "Please enter the OTP code before submit!",
          },
        ]}
      >
        <Input.OTP />
      </Form.Item>
      <Form.Item>
        <Button
          htmlType="submit"
          className="w-full"
          type="primary"
          size="large"
        >
          Submit
        </Button>
        <Button
          htmlType="button"
          className="w-full mt-5"
          type="default"
          size="large"
          disabled={timeLeft == 0 ? false : true}
          onClick={() => handleResendOTPCode()}
        >
          Resend OTP Code ({timeLeft ?? ""})
        </Button>
      </Form.Item>
    </Form>
  </Modal>
</>

);
}

export default SignInForm

Log output

image

aws-exports.js

// .env file:
NEXT_PUBLIC_BASE_PATH=/public-portal
NEXT_PUBLIC_AWS_COGNITO_DOMAIN=https://cognito-idp.ap-southeast-1.amazonaws.com/ap-southeast-1_xxxxxx
NEXT_PUBLIC_AWS_COGNITO_IDENTITY_POOL_ID=ap-southeast-1:xxxxxx
NEXT_PUBLIC_AWS_COGNITO_USER_POOL_ID=ap-southeast-1_xxxxxxxx
NEXT_PUBLIC_AWS_COGNITO_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
NEXT_PUBLIC_DOMAIN_NOT_SECURE=192.xxx.x.xxx:xxxx # for not secure
NEXT_PUBLIC_DOMAIN_SECURE=dev.xxxxx.com:xxxx # for secure

export const getAmplifyConfig = (domain: string = ${process.env.NEXT_PUBLIC_DOMAIN_NOT_SECURE}): ResourcesConfig => {
const amplifyConfig: ResourcesConfig = {
Auth: {
Cognito: {
// identityPoolId: process.env.NEXT_PUBLIC_AWS_COGNITO_IDENTITY_POOL_ID ?? '',
userPoolId: process.env.AWS_COGNITO_USER_POOL_ID ?? "",
userPoolClientId: process.env.AWS_COGNITO_CLIENT_ID ?? "",
loginWith: {
oauth: {
domain: process.env.AWS_COGNITO_DOMAIN ?? "",
scopes: [
"email",
"openid",
"profile",
"aws.cognito.signin.user.admin",
],
redirectSignIn: [http://${domain}${process.env.NEXT_PUBLIC_BASE_PATH}/signin],
redirectSignOut: [http://${domain}${process.env.NEXT_PUBLIC_BASE_PATH}/sign-out],
responseType: "token",
},
email: false,
username: true,
phone: false,
}
},

  /*
  API: {
    endpoints: [
      {
        name: 'talent',
        endpoint: process.env.NEXT_PUBLIC_AWS_TALENT_API_DOMAIN,
        custom_header: authen_header,
      },
    ],
  },
  */
},

};

return amplifyConfig;
};

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

@github-actions github-actions bot added pending-triage Issue is pending triage pending-maintainer-response Issue is pending a response from the Amplify team. labels Oct 25, 2024
@cwomack cwomack self-assigned this Oct 25, 2024
@cwomack cwomack added Next.js Auth Related to Auth components/category question General question and removed pending-triage Issue is pending triage labels Oct 25, 2024
@cwomack
Copy link
Member

cwomack commented Oct 25, 2024

Hello, @cong-tri 👋. There's a lot of details in this issue and we might need to break down to determine the root cause. To start, can you let me know what documentation you're following or where you started with this?

One issue might be that there's some discrepancies on how you're configuring MFA (placing it within the scopes for OAuth, but then marking it as false for email for example. Can you clarify what you're looking to implement for your authentication flows?

Second thing that stands out is the use of http:// outside of your environment variables as well as setting secure: false within your custom CookieStorage. If you change the cookie attributes on the client side, they may be overwritten by the server side attributes (we currently do don't support configuring attributes on the server side). You should only have to use the ssr: true flag rather than needing to create a customer CookieStorage yourself.

Is there any chance you could create a sample repo to share that we can make suggestions to the most recent code being used and for reproducing the exceptions you're getting?

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Oct 25, 2024
@cwomack cwomack added the pending-community-response Issue is pending a response from the author or community. label Oct 25, 2024
@cong-tri
Copy link
Author

cong-tri commented Oct 28, 2024

For starting, i am following aws amplify nextjs documentation section "https://docs.amplify.aws/nextjs/build-a-backend/server-side-rendering/" for my task.
Step 1: I made a component to login to the website. In that component I will call the signIn function to pass in the username and password.
Step 2: And if I get the "Duplicate Device" error, I will call the signIn function again and pass an additional parameter mfaMethod: "Email" to the clientMetadata of options so that I can email the user an OTP code for authentication.
Step 3: Finally, I will call the confirmSignIn function to pass the challengeResponse: OTP code; to complete the final authentication screen to log in.
But I'm having a problem with the last step: when I call the confirmSignIn function to pass in the challegeResponse: OTP code, the browser reports the error "Unable to get user session after successful sign-in.". I had to stop at this step for a while.
I'm willing to try many ways to solve the problem so please give me suggestions.

@github-actions github-actions bot added pending-maintainer-response Issue is pending a response from the Amplify team. and removed pending-community-response Issue is pending a response from the author or community. labels Oct 28, 2024
@cong-tri
Copy link
Author

cong-tri commented Oct 28, 2024

If i setup :
Amplify.configure(config, {
// ssr: true,
});
// cognitoUserPoolsTokenProvider.setKeyValueStorage(
// new CookieStorage({
// domain: http://${process.env.DOMAIN_NOT_SECURE},
// path: ${process.env.BASE_PATH},
// secure: false,
// // sameSite: "strict",
// })
// );
it could be signed in successfully but it stored some parameters from CognitoIdentityServiceProvider return at localStorage
It is not at cookie storage.

And if i enable ssr: true it could not work for me. It just browser return a error "Unable to get user session after successful sign-in." in the last step.

@cwomack
Copy link
Member

cwomack commented Oct 29, 2024

@cong-tri, are you able to also share what the network response form Cognito is when you get that initial 400 status code and the confirmSignIn() call? And if you're building a sample app from the docs, any chance you can share a sample repo that we could test with as well?

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Oct 29, 2024
@cwomack cwomack added the pending-community-response Issue is pending a response from the author or community. label Oct 29, 2024
@cwomack
Copy link
Member

cwomack commented Nov 6, 2024

@cong-tri, wanted to check in again and see if you saw the above comment and questions.

@cwomack
Copy link
Member

cwomack commented Nov 12, 2024

Closing this issue as we have not heard back from you. If you are still experiencing this, please feel free to reply back and provide any information previously requested and we'd be happy to re-open the issue.

Thank you!

@cwomack cwomack closed this as completed Nov 12, 2024
@github-actions github-actions bot removed the pending-community-response Issue is pending a response from the author or community. label Nov 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Auth Related to Auth components/category Next.js question General question
Projects
None yet
Development

No branches or pull requests

2 participants