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.42k stars 2.12k forks source link

Auth.currentAuthenticatedUser does not work in NextJs API routes #10818

Closed asp3 closed 10 months ago

asp3 commented 1 year ago

Before opening, please confirm:

JavaScript Framework

React, Next.js

Amplify APIs

Authentication

Amplify Categories

auth

Environment information

@aws-amplify/api: ^5.0.7 => 5.0.7 
@aws-amplify/auth: ^5.1.1 => 5.1.1 
@aws-amplify/core: ^5.0.7 => 5.0.7 
@aws-amplify/storage: ^5.0.7 => 5.0.7 

Describe the bug

The authenticated user is not returned in an API route, even though it is returned on client side and getServerSideProps

Expected behavior

When used as

export async function getServerSideProps(context) {
    const { API, Auth } = withSSRContext(context);

on a nextJs page, this works as expected. However, in API routes, it seems to return an error.

Reproduction steps

Set up a route in API/anyRoute.

Hit the API from nextJs client side.

See that the authenticated user is not returned.

Code Snippet

import { buffer } from "micro";
import Cors from "micro-cors";
import { NextApiRequest, NextApiResponse } from "next";
import { withSSRContext } from "aws-amplify";
import Amplify from "@aws-amplify/core";
import aws_exports from "@/aws_exports";

import Stripe from "stripe";
import { oauth, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET } from "@/deploy_constants";
const stripe = new Stripe(STRIPE_SECRET_KEY, {
    // https://github.com/stripe/stripe-node#configuration
    apiVersion: "2020-08-27",
});

const webhookSecret: string = STRIPE_WEBHOOK_SECRET;
// Amplify SSR configuration needs to be enabled within each API route
Amplify.configure({
    ...aws_exports,
    oauth: {
        ...oauth,
    },
    ssr: true,
});
// Stripe requires the raw body to construct the event.
export const config = {
    api: {
        bodyParser: false,
    },
};

const cors = Cors({
    allowMethods: ["POST", "HEAD"],
});

const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
    console.log("REQ", req.cookies);
    const { Auth } = withSSRContext({ req });
    try {
        const user = await Auth.currentAuthenticatedUser();
        console.log("GOT", user);
    } catch (e) {
        console.log("ERERER", e);
    }
    if (req.method === "POST") {
        const buf = await buffer(req);
        const sig = req.headers["stripe-signature"]!;

        let event: Stripe.Event;

        try {
            event = stripe.webhooks.constructEvent(buf.toString(), sig, webhookSecret);
        } catch (err) {
            const errorMessage = err instanceof Error ? err.message : "Unknown error";
            // On error, log and return the error message.
            if (err! instanceof Error) console.log(err);
            console.log(`āŒ Error message: ${errorMessage}`);
            res.status(400).send(`Webhook Error: ${errorMessage}`);
            return;
        }

        // Successfully constructed event.
        console.log("āœ… Success:", event.id);

        // Cast event data to Stripe object.
        if (event.type === "invoice.payment_failed") {
            // TODO: grant user access until checkoutSession.expires_at
            const invoice = event.data.object as Stripe.Invoice;
            console.log(`āŒ Payment failed:  ${invoice.subscription}`);
        } else if (event.type === "invoice.paid") {
            // TODO: grant user access until invoice.period_end
            const invoice = event.data.object as Stripe.Invoice;
            console.log(
                `šŸ’µ Paid successfully. ${invoice.subscription}: FROM ${invoice.period_start} TO ${invoice.period_end}`
            );
        } else {
            console.warn(`šŸ¤·ā€ā™€ļø Unhandled event type: ${event.type}`);
        }

        // Return a response to acknowledge receipt of the event.
        res.json({ received: true });
    } else {
        res.setHeader("Allow", "POST");
        res.status(405).end("Method Not Allowed");
    }
};

export default cors(webhookHandler as any);

Log output

REQ {}
ERERER The user is not authenticated

Seems like req.cookies are not set.

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

nadetastic commented 1 year ago

Hi @asp3 thank you for opening this issue.

Its a bit interesting for me as initially I was successfully getting the signed in user until I actively signed out from my test app. After signing back in again I am consistently getting The user is not authenticated on the API route along with the error unhandledRejection: ReferenceError: window is not defined - However, my request cookies are available.

Some additional question for you

I will continue to look into this further and get back to you - here's a sample of my test app

asp3 commented 1 year ago

Hi @nadetastic thanks for the response on this issue as well! My oauth object is pretty basic,

export const oauth = {
    domain: "authdev.xxxx",
    scopes: ["phone", "email", "openid", "profile", "aws.cognito.signin.user.admin"],
    redirectSignIn,
    redirectSignOut,
    responseType: "code",
};

Originally, I only configure it once, in my _app.js, but I read somewhere that it needs to be configured in each API route as well, so I added it there. My app supports Google, Apple, and FB, but for the sake of my testing, I was just using basic authentication through email and password. The especially weird part is that on client side AND server side (getServerSideProps), the user is returned successfully, this issue only seems to be from the API routes.

Thanks for helping investigate this issue!

asp3 commented 1 year ago

@nadetastic Also one more thing, calling API.graphql (from SSR) from seems to also return an Error: No current user, even though

import { withSSRContext } from "aws-amplify";

export async function getServerSideProps(context) {
    const { API, Auth } = withSSRContext(context);

    // runs successfully, prints all data
    console.log(await Auth.currentAuthenticatedUser());
    console.log(await Auth.currentSession());

    // throws error 
    API.graphql({
            query: gql(getCurrentUser),
            variables: {
                input: {
                    userId,
                },
            },
            limit: 1000,
            authMode: "AMAZON_COGNITO_USER_POOLS"
        });
}

both console logs work properly, but calling the API throws the errors

nadetastic commented 1 year ago

Hmm i'm not able to reproduce the error from the API - and for the original error via the api route, I'm no longer have this issue.

Could you provide more context on the app? Such as the dependencies and their versions. Also is the issue on #10819 and here occurring on the same app?

nadetastic commented 1 year ago

@asp3 meant to mention you on the above comment

asp3 commented 1 year ago

Sorry for the late response, but yes, #10819 is also happening in the same app. The dependencies are listed in the issue too,

@aws-amplify/api: ^5.0.7 => 5.0.7 @aws-amplify/auth: ^5.1.1 => 5.1.1 @aws-amplify/core: ^5.0.7 => 5.0.7 @aws-amplify/storage: ^5.0.7 => 5.0.7

For us, this is happening very sporadically as well. We have around 30,000 calls made, and around 150 errors based on our loggings, which seems like its a very specific case. I was only able to reproduce it once locally. We are thinking this is causing some other issues as well, since the client shows logged in, but ssr returns no current user.

When using SSR on a normal page, and then in some API route, both worked for you as expected?

@nadetastic

nadetastic commented 1 year ago

@asp3 Correct, this work for me on both SSR pages (using getServerSideProps) and on /api routes

weisisheng commented 1 year ago

Sorry for the late response, but yes, #10819 is also happening in the same app. The dependencies are listed in the issue too,

@aws-amplify/api: ^5.0.7 => 5.0.7 @aws-amplify/auth: ^5.1.1 => 5.1.1 @aws-amplify/core: ^5.0.7 => 5.0.7 @aws-amplify/storage: ^5.0.7 => 5.0.7

For us, this is happening very sporadically as well. We have around 30,000 calls made, and around 150 errors based on our loggings, which seems like its a very specific case. I was only able to reproduce it once locally. We are thinking this is causing some other issues as well, since the client shows logged in, but ssr returns no current user.

When using SSR on a normal page, and then in some API route, both worked for you as expected?

@nadetastic

Any more insights on this? Trying to create a protected page and console.log of signed-in user reflects the correct info (since I signed in from a prior page) but try/catch to return the authenticated user fails. I am also referencing aws-exports.js on every page.

  "dependencies": {
    "@aws-amplify/ui-react": "^4.3.8",
    "aws-amplify": "^5.0.16",
    "next": "13.2.1",
    "react": "18.2.0",
    "react-dom": "18.2.0"
  },
  "devDependencies": {
    "@types/react": "18.0.28",
    "eslint": "8.35.0",
    "eslint-config-next": "13.2.1",
    "typescript": "4.9.5"
  }
nadetastic commented 1 year ago

Hi @weisisheng - to clarify is this happening on all requests or sporadically? Also only when using withSSRContext in a server side method (i.e getServerSideProps()) ?

molandim commented 1 year ago

I am having the same problem as well.

The authentication works fine on the client-side, but as soon as any page is redirected after a successful authentication and the SSR enters in action, the user is undefined.

dependency:

 "dependencies": {
    "@aws-amplify/cache": "^5.0.16",
    "@aws-amplify/ui-react": "^4.3.8",

    "@types/aws-lambda": "^8.10.82",
    "@vendia/serverless-express": "^4.3.11",
    "aws-amplify": "^5.0.16",
    "aws-lambda": "^1.0.6",
    "react": "^17.0.2",

  }

_app.js


Amplify.configure(
  ssr: true,
  Auth: {
    region: 'eu-west-2',
    userPoolId: process.env.NEXT_PUBLIC_USER_POOL_ID,
    userPoolWebClientId: process.env.NEXT_PUBLIC_USER_POOL_WEB_CLIENT_ID,
    mandatorySignIn: true,
    authenticationFlowType: 'USER_PASSWORD_AUTH',
  },
});

internal page:

export async function getServerSideProps(context: GetServerSidePropsContext): Promise<void | any> {
  const { Auth } = withSSRContext(context);

  //Auth.configure(authConfiguration.Auth);
  let user;
  try {
    console.log('will get the use');
    user = await Auth.currentAuthenticatedUser();
  } catch (err) {
    console.log('šŸš€ ~ file: auth.helpers.tsx:16 ~ serverSideRedirectNoAuth ~ err:', err);
  }

  if (!user) {
    return {
      redirect: {
        permanent: false,
        destination: `/login`,
      },
    };
  }

  return { props: {} };
}
nadetastic commented 1 year ago

@molandim What version of nextjs are you using?

weisisheng commented 1 year ago
  const { Auth } = withSSRContext({ req });
    try {
        const user = await Auth.currentAuthenticatedUser();
        console.log("GOT", user);
    } catch (e) {
        console.log("ERERER", e);
    }

Apologies for the delay. My package.json is:

{ "name": "foo4", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "@aws-amplify/ui-react": "^4.4.3", "aws-amplify": "^5.0.23", "date-fns": "^2.29.3", "next": "13.2.4", "react": "18.2.0", "react-dom": "18.2.0" }, "devDependencies": { "eslint": "8.37.0", "eslint-config-next": "13.2.4" } }

Stepped away from this project, then due to issues with amplify deploy taking so long, I reverted to trying to create everything on CDK but the front end. Same problem. User is logged in but SSR requests indicate no user.

Happening on all requests.

"Also only when using withSSRContext in a server side method (i.e getServerSideProps()) ?"

Yes

aliza-khu commented 1 year ago

@tannerabread @AllanZhengYP, Can you please look into this issue(https://github.com/aws-amplify/amplify-js/issues/11156)? it requires urgent attention.

cwoolum commented 1 year ago

I'm getting this also when using server side components with the new app router that is about to be released.

Here's the code I'm using

import "@aws-amplify/ui-react/styles.css";

import { Amplify, Auth } from "aws-amplify";
import { redirect } from "next/navigation";

import awsExports from "../../aws-exports";

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

export const metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

async function checkAuth() {
  try {
    const user = await Auth.currentAuthenticatedUser();
    console.log(user);

    return true;
  } catch (e) {}

  return false;
}

export default async function ShowcaseLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const isAuthenticated = await checkAuth();

  if (!isAuthenticated) {
    redirect("/login");
  }

  return children;
}

and the logs when it fails.

error - node_modules/@aws-amplify/auth/lib/OAuth/oauthStorage.js (6:0) @ exports.setState
error - unhandledRejection: Error [ReferenceError]: window is not defined
    at exports.setState (webpack-internal:///(sc_client)/./node_modules/@aws-amplify/auth/lib/OAuth/oauthStorage.js:6:5)
    at OAuth.oauthSignIn (webpack-internal:///(sc_client)/./node_modules/@aws-amplify/auth/lib/OAuth/OAuth.js:46:22)
    at AuthClass.eval (webpack-internal:///(sc_client)/./node_modules/@aws-amplify/auth/lib/Auth.js:2184:48)
    at step (webpack-internal:///(sc_client)/./node_modules/@aws-amplify/auth/node_modules/tslib/tslib.es6.js:126:23)
    at Object.eval [as next] (webpack-internal:///(sc_client)/./node_modules/@aws-amplify/auth/node_modules/tslib/tslib.es6.js:107:53)
    at eval (webpack-internal:///(sc_client)/./node_modules/@aws-amplify/auth/node_modules/tslib/tslib.es6.js:100:71)
    at new Promise (<anonymous>)
    at Module.__awaiter (webpack-internal:///(sc_client)/./node_modules/@aws-amplify/auth/node_modules/tslib/tslib.es6.js:96:12)
    at AuthClass.federatedSignIn (webpack-internal:///(sc_client)/./node_modules/@aws-amplify/auth/lib/Auth.js:2150:24)
    at Login (webpack-internal:///(sc_client)/./src/app/login/page.tsx:17:51)
    at renderWithHooks (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8367:16)
    at renderIndeterminateComponent (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8441:15)
    at renderElement (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8676:7)
    at renderLazyComponent (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8656:3)
    at renderElement (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8766:11)
    at renderNodeDestructiveImpl (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8843:11)
    at renderNodeDestructive (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8815:14)
    at renderNode (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:9020:12)
    at renderChildrenArray (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8972:7)
    at renderNodeDestructiveImpl (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8880:7)
    at renderNodeDestructive (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8815:14)
    at renderContextProvider (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8640:3)
    at renderElement (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8754:11)
    at renderNodeDestructiveImpl (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8843:11)
    at renderNodeDestructive (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8815:14)
    at finishClassComponent (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8397:3)
    at renderClassComponent (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8405:3)
    at renderElement (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8673:7)
    at renderNodeDestructiveImpl (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8843:11)
    at renderNodeDestructive (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8815:14)
    at renderIndeterminateComponent (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8495:7)
    at renderElement (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8676:7)
    at renderNodeDestructiveImpl (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8843:11)
    at renderNodeDestructive (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8815:14)
    at finishClassComponent (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8397:3)
    at renderClassComponent (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8405:3)
    at renderElement (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8673:7)
    at renderNodeDestructiveImpl (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8843:11)
    at renderNodeDestructive (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8815:14)
    at renderIndeterminateComponent (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8495:7)
    at renderElement (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8676:7)
    at renderNodeDestructiveImpl (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8843:11)
    at renderNodeDestructive (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8815:14)
    at renderElement (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8702:9)
    at renderNodeDestructiveImpl (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8843:11)
    at renderNodeDestructive (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8815:14)
    at renderIndeterminateComponent (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8495:7)
    at renderElement (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8676:7)
    at renderNodeDestructiveImpl (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8843:11)
    at renderNodeDestructive (/local/home/woolumc/scratch/test2/test/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:8815:14) {
  digest: undefined
}
asp3 commented 1 year ago

bumping again as it is happening again for me.

On client side, all Auth calls work as expected, but from SSR,

SSR.Auth.currentAuthenticatedUser throws the error The user is not authenticated

bsor-dev commented 1 year ago

Same issue

lavrton commented 1 year ago

I had the same issue. After hours of debugging, we found that we have two places of code where Auth.configure was called differently.

In one route (with usage of getServerSideProps):

Auth.configure({ ...authConfig.Auth, ssr: true });

In another (where we used only client side):

Auth.configure({ ...authConfig.Auth });

After adding ssr: true into the second case, it resolved the problem. The issue was not reproducible on local dev env with next.js. Only the production version had this issue. I assume it is somehow related to page preloading in nextjs.

foobarnes commented 1 year ago

I'm also seeing this in a Nextjs API route. I've configured Amplify with:

import awsConfig from 'src/aws-exports';
Amplify.configure({ ...awsConfig, ssr: true });

but I'm still seeing unauthenticated credentials:

{
  identityId: '...',
  accessKeyId: '...',
  secretAccessKey: '...',
  sessionToken: '...',
  expiration: 2023-07-05T16:10:59.000Z,
  authenticated: false
}
117 commented 1 year ago

still occurring. next-js app router middleware. cannot use even with SSR enabled in Amplify config

weisisheng commented 1 year ago

Same here. This is really a show stopper. Tried a number of different things. Isn't this a common problem for more devs using 13.4x and a combo of client and server?

abdallahshaban557 commented 1 year ago

Hello everyone, we are currently working on enabling better support for NextJS API routes, middleware, and React Server Components with App Router. We have published our Areas of Focus for 2023 as well, and this is top of mind for us. We are currently testing our approach, since we want to start off with delivering a developer preview version to get feedback on our developer experience.

117 commented 1 year ago

Hello everyone, we are currently working on enabling better support for NextJS API routes, middleware, and React Server Components with App Router. We have published our Areas of Focus for 2023 as well, and this is top of mind for us. We are currently testing our approach, since we want to start off with delivering a developer preview version to get feedback on our developer experience.

Do you know of any temporary workarounds? Or a different library I can use Cognito with SSR?

nadetastic commented 11 months ago

The developer preview for v6 of Amplify has officially been released with improvements to SSR support and much more! Please check out our announcement and updated documentation to see what has changed.

This issue should be resolved within the dev preview and upcoming General Availability for Amplify v6, but let us know with a comment if there are further issues.

117 commented 11 months ago

will try, thank you for the update

cwomack commented 10 months ago

With the release of the latest major version of Amplify (aws-amplify@>6), this issue should now be resolved! Please refer to our release announcement, migration guide, and documentation for more information.

KOSSOKO commented 7 months ago

Hello @cwomack, we have the exact same issue but with Angular. We even open a premium support ticket. After days, we just create a stackoverflow issue (https://stackoverflow.com/questions/78009145/amplify-ssr-error-authuserpoolexception-auth-userpool-not-configured) because we plan to deploy our solution 1st of March.

Please, for what reasons we have this AuthUserPoolException: Auth UserPool not configured when we switch to SSR