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

runWithAmplifyServerContext with 'cookies' context not working in Server Component #13886

Closed
3 tasks done
michaelhlf opened this issue Oct 4, 2024 · 6 comments
Closed
3 tasks done
Assignees
Labels
Gen 2 Issues related to Gen 2 Amplify projects GraphQL Related to GraphQL API issues not-reproducible Not able to reproduce the issue

Comments

@michaelhlf
Copy link

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: Linux 6.5 Ubuntu 20.04.1 LTS (Focal Fossa)
    CPU: (8) x64 11th Gen Intel(R) Core(TM) i7-11370H @ 3.30GHz
    Memory: 9.69 GB / 15.36 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 20.16.0 - ~/.nvm/versions/node/v20.16.0/bin/node
    npm: 10.8.1 - ~/.nvm/versions/node/v20.16.0/bin/npm
  Browsers:
    Brave Browser: 129.1.70.123
    Chrome: 127.0.6533.99
    Chromium: 127.0.6533.119
  npmPackages:
    @ampproject/toolbox-optimizer:  undefined ()
    @aws-amplify/adapter-nextjs: ^1.2.14 => 1.2.14 
    @aws-amplify/adapter-nextjs/api:  undefined ()
    @aws-amplify/adapter-nextjs/data:  undefined ()
    @aws-crypto/sha256-js: ^5.2.0 => 5.2.0 
    @aws-sdk/client-cognito-identity-provider: ^3.651.1 => 3.651.1 
    @aws-sdk/client-dynamodb: ^3.656.0 => 3.656.0 
    @aws-sdk/client-s3: ^3.626.0 => 3.626.0 
    @aws-sdk/client-sesv2: ^3.624.0 => 3.624.0 
    @aws-sdk/lib-dynamodb: ^3.656.0 => 3.656.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 ()
    @pulumi/aws: ^6.52.0 => 6.52.0 
    @types/js-cookie: ^3.0.6 => 3.0.6 
    @types/node: ^20 => 20.14.14 
    @types/react: ^18 => 18.3.3 
    @types/react-dom: ^18 => 18.3.0 
    @types/react-transition-group: ^4.4.10 => 4.4.10 
    @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.20 
    aws-amplify: ^6.5.2 => 6.5.2 
    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 ()
    classnames: ^2.5.1 => 2.5.1 
    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 ()
    edge-runtime:  undefined ()
    embla-carousel: ^8.0.0 => 8.1.8 
    embla-carousel-autoplay: ^8.0.0 => 8.1.8 
    embla-carousel-react: ^8.0.0 => 8.1.8 
    eslint: ^8 => 8.57.0 
    eslint-config-next: 14.1.4 => 14.1.4 
    events:  undefined ()
    example:  0.0.0 
    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 ()
    js-cookie: ^3.0.5 => 3.0.5 
    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 
    node-fetch:  undefined ()
    node-html-parser:  undefined ()
    ora:  undefined ()
    os-browserify:  undefined ()
    p-limit:  undefined ()
    path-browserify:  undefined ()
    platform:  undefined ()
    postcss: ^8 => 8.4.41 (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-otp-input: ^3.1.1 => 3.1.1 
    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 ()
    react-transition-group: ^4.4.5 => 4.4.5 
    react-transition-group/CSSTransition:  undefined ()
    react-transition-group/ReplaceTransition:  undefined ()
    react-transition-group/SwitchTransition:  undefined ()
    react-transition-group/Transition:  undefined ()
    react-transition-group/TransitionGroup:  undefined ()
    react-transition-group/TransitionGroupContext:  undefined ()
    react-transition-group/config:  undefined ()
    react-verification-input: ^4.1.2 => 4.1.2 
    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.5 => 0.33.5 
    shell-quote:  undefined ()
    source-map:  undefined ()
    sst: ^3.1.51 => 3.1.54 
    stacktrace-parser:  undefined ()
    stream-browserify:  undefined ()
    stream-http:  undefined ()
    string-hash:  undefined ()
    string_decoder:  undefined ()
    strip-ansi:  undefined ()
    stripe: ^16.12.0 => 16.12.0 
    superstruct:  undefined ()
    tailwindcss: ^3.3.0 => 3.4.8 
    tar:  undefined ()
    terser:  undefined ()
    text-table:  undefined ()
    timers-browserify:  undefined ()
    tty-browserify:  undefined ()
    typescript: ^5 => 5.5.4 
    ua-parser-js:  undefined ()
    unistore:  undefined ()
    util:  undefined ()
    vm-browserify:  undefined ()
    watchpack:  undefined ()
    web-vitals:  undefined ()
    webpack:  undefined ()
    webpack-sources:  undefined ()
    ws:  undefined ()
    zod: ^3.23.5 => 3.23.8 ()
  npmGlobalPackages:
    corepack: 0.28.2
    npm: 10.8.1
    pulumi: 0.0.1


Describe the bug

When using the runWithAmplifyServerContext ( in order to run fetchAuthSession(contextSpec)) created with createServerRunner() in a Nextjs 14 Server component, using {cookies} as a context causes a stack overflow error. runWithAmplifyServerContext works as expected when called from Nextjs middleware (middleware.ts), again passing in {cookies} as context, as imported with 'import { cookies } from 'next/headers';'

Expected behavior

calling my authenticatedUser() function (which uses runWithAmplifyServerContext ) should return the current user session info as it does when calling from middleware.ts

Reproduction steps

  1. Setup a NextJs project which uses AWS Amplify sdk manage users and sessions with cognito.
  2. create a server runner using createServerRunner, imported from @aws-amplify/adapter-nextjs
  3. create a function 'authenticatedUser' which takes a NextServer.Context object and uses it to run fetchAuthSession(contextSpec)
  4. import this function into a page.tsx file and call the function from within a server component in this file, passing in {cookies} as the context spec as imported from 'next/headers'
  5. Observe the stack overflow error occuring

Code Snippet

in utils/amplify-server-utils.ts

import { authConfig } from "../amplify-cognito-config";
import { NextServer, createServerRunner } from "@aws-amplify/adapter-nextjs";
import { fetchAuthSession, getCurrentUser } from "aws-amplify/auth/server";




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

// Existing function to get the authenticated user
export async function authenticatedUser(context: NextServer.Context, refreshIfNotSubscriber = false) {
  return await runWithAmplifyServerContext({
    nextServerContext: context,
    operation: async (contextSpec) => {
      try {
        let session = await fetchAuthSession(contextSpec);
        if (!session.tokens) {
          return;
        }
        let user = {
          ...(await getCurrentUser(contextSpec)),
          isSubscribed: false,
        };
        let groups = session.tokens.accessToken.payload["cognito:groups"];
        // @ts-ignore
        user.isSubscribed = Boolean(groups && groups.includes("Subscriber"));

        if (refreshIfNotSubscriber && !user.isSubscribed) {
          session = await fetchAuthSession(contextSpec, { forceRefresh: true });
          if (!session.tokens) {
            return;
          }
          user = {
            ...(await getCurrentUser(contextSpec)),
            isSubscribed: false,
          };
          groups = session.tokens.accessToken.payload["cognito:groups"];
          // @ts-ignore
          user.isSubscribed = Boolean(groups && groups.includes("Subscriber"));
        }

        return user;
      } catch (error) {
        console.log(error);
      }
    },
  });
}

And in learn/page.tsx

import React from 'react';
import Layout from '../components/Layout';
import ProgressBar from "../components/progressBar";
import { authenticatedUser } from 'utils/amplify-server-utils';
import { cookies } from 'next/headers';


export const metadata: Metadata = {
  title: 'Learn',
  description: "Browse our learning paths"
};



const Learn = async () => {
  const user = await authenticatedUser({cookies})
  return (
    <Layout pageTitle='Learning Paths'>
      <div className='w-80 h-3'>
        <ProgressBar progress={80} />
      </div>
    </Layout>
  );
};

export default Learn;


Log output

When the authenticatedUser function is called from the server component, the following error occurs :
RangeError: Maximum call stack size exceeded
at Array.values (<anonymous>)
at deepFreeze (/home/michael/Documents/Side Project/pre-launch-website/.next/server/chunks/node_modules_@aws-amplify_core_dist_esm_583a85_.js:4146:24)
at deepFreeze (/home/michael/Documents/Side Project/pre-launch-website/.next/server/chunks/node_modules_@aws-amplify_core_dist_esm_583a85_.js:4149:13)
at deepFreeze (/home/michael/Documents/Side Project/pre-launch-website/.next/server/chunks/node_modules_@aws-amplify_core_dist_esm_583a85_.js:4149:13)
at deepFreeze (/home/michael/Documents/Side Project/pre-launch-website/.next/server/chunks/node_modules_@aws-amplify_core_dist_esm_583a85_.js:4149:13)
...

As mentioned, when the same function is called from middleware, passing in the cookies (imported from next/headers in exactly the same way ) everything works as expected

Interestingly, the value of 'cookies' does seem to be different, when simply using console.log to show the cookies object (or function?) depending if it is logged from middleware or the server component.

From the middleware

cookies in middleware.ts: function cookies() {
if (__TURBOPACK imported module __$5b$project$5d2f$node_modules$2f$next$2f$dist$2f$esm$2f$client$2f$components$2f$static$2d$generation$2d$bailout$2e$js__$5b$middleware$5d$_$28$ecmascript$29$__
link: "https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering"
return __TURBOPACK__imported module _$5b$project$5d2f$node_modules$2f$next$2f$dist$2f$esm$2f$server$2f$web$2f$spec$2d$extension$2f$adapters$2f$request$2d$cookies$2e$js__$5b$middleware$Sd$_$28$ecmascript$29$__["RequestCookiesAdapter"]
deal(new _ TURBOPACK imported module _ $5b$project$5d2f$node_modules$2f$next$2F$dist$2F$compiled$2f40Sedge$2d$runtime$2F$cookies$2F$index$2e$js__$5b$middlewaresSd$_$28$ecmascript$29$_["RequestCookies"](new Headers({})));
}

const requestStore = _TURBOPACK imported module $5b$project$5d2f$node_modules$2f$next$2f$dist$2f$esm$2f$client$2f$components$2f$request$2dsasync$2d$storages2esexternal$2e$js_$Sb$middleware$Sd$_$28$ecmascript$29$__["requestAsyncStorage"].getStore();
if (!requestStore) {
throw new Error("Invariant: cookies() expects to have requestAsyncStorage, none available.");
}

const asyncActionStore = _ TURBOPACK imported _module_$5b$project$5d2f$node_modules$2f$next$2f$dist$2f$esm$2f$client$2f$components$2F$actions2dsasync$2d$storages2eSexternal$2e$js_$5b$middleware$Sd$_$28$ecmascript$29$__["actionAsyncStorage"].getStore();
if (asyncActionStore && (asyncActionStore.isAction || asyncActionStore.isAppRoute)) {
// We can't conditionally return different types here based on the context.
// To avoid confusion, we always return the readonly type here.
return requestStore.mutableCookies;
}

return requestStore.cookies;

and from the Server component

cookies in server component: [Function: cookies]


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

This occurs both locally, using next dev, and when deployed to my live environment. This is deployed to AWS using SST Ion

@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 4, 2024
@michaelhlf
Copy link
Author

As a reference, I largely followed this guide for my setup https://docs.amplify.aws/javascript/build-a-backend/server-side-rendering/nextjs-app-router-server-components/

@chrisbonifacio chrisbonifacio self-assigned this Oct 4, 2024
@chrisbonifacio chrisbonifacio added to-be-reproduced Used in order for Amplify to reproduce said issue Gen 2 Issues related to Gen 2 Amplify projects GraphQL Related to GraphQL API issues and removed pending-triage Issue is pending triage labels Oct 4, 2024
@chrisbonifacio
Copy link
Member

Hi @michaelhlf 👋 thanks for raising this issue and providing detailed reproduction steps!

I will attempt to reproduce and will report back with any findings.

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Oct 4, 2024
@chrisbonifacio
Copy link
Member

chrisbonifacio commented Oct 8, 2024

Hi @michaelhlf 👋

A quick update - Unfortunately, I was unable to reproduce the issue on the latest version of the aws-amplify library.

I tried creating a Next.js app with a similar setup and did not run into issues executing the provided code.

I had to edit the server util file and Server Component a little bit because I don't have the same config file or custom UI components so I just rendered the result of the authenticatedUser call.

I did not observe a stack overflow error in the browser console or dev server.

I would recommend checking the logic in your components that might be causing an infinite re-render or calling of the function. At the moment, it does not appear to be an issue caused by the Amplify library.

Here's the way I configured Amplify in the server utils:

import amplify_outputs from "@/amplify_outputs.json";
import { NextServer, createServerRunner } from "@aws-amplify/adapter-nextjs";
import { fetchAuthSession, getCurrentUser } from "aws-amplify/auth/server";

export const { runWithAmplifyServerContext } = createServerRunner({
  config: {
    Auth: {
      Cognito: {
        userPoolId: amplify_outputs.auth.user_pool_id,
        userPoolClientId: amplify_outputs.auth.user_pool_client_id,
        identityPoolId: amplify_outputs.auth.identity_pool_id,
      },
    },
  },
});

// Existing function to get the authenticated user
export async function authenticatedUser(
  context: NextServer.Context,
  refreshIfNotSubscriber = false
) {
  return await runWithAmplifyServerContext({
    nextServerContext: context,
    operation: async (contextSpec) => {
      try {
        let session = await fetchAuthSession(contextSpec);
        if (!session.tokens) {
          return;
        }
        let user = {
          ...(await getCurrentUser(contextSpec)),
          isSubscribed: false,
        };
        let groups = session.tokens.accessToken.payload["cognito:groups"];
        // @ts-ignore
        user.isSubscribed = Boolean(groups && groups.includes("Subscriber"));

        if (refreshIfNotSubscriber && !user.isSubscribed) {
          session = await fetchAuthSession(contextSpec, { forceRefresh: true });
          if (!session.tokens) {
            return;
          }
          user = {
            ...(await getCurrentUser(contextSpec)),
            isSubscribed: false,
          };
          groups = session.tokens.accessToken.payload["cognito:groups"];
          // @ts-ignore
          user.isSubscribed = Boolean(groups && groups.includes("Subscriber"));
        }

        return user;
      } catch (error) {
        console.log(error);
      }
    },
  });
}

Here's my server component:

import React from "react";
import { authenticatedUser } from "@/utils/amplify-server-utils";
import { cookies } from "next/headers";
import { Metadata } from "next";

export const metadata: Metadata = {
  title: "Learn",
  description: "Browse our learning paths",
};

const Learn = async () => {
  const user = await authenticatedUser({ cookies });
  return (
    <div className="w-80 h-3">
      <pre>{JSON.stringify(user, null, 2)}</pre>
    </div>
  );
};

export default Learn;

When I navigate to /learn endpoint after signing in, I'm able to see the user session info.

CleanShot 2024-10-08 at 12 52 38

@chrisbonifacio chrisbonifacio added not-reproducible Not able to reproduce the issue pending-community-response Issue is pending a response from the author or community. and removed to-be-reproduced Used in order for Amplify to reproduce said issue labels Oct 8, 2024
@michaelhlf
Copy link
Author

Hi @chrisbonifacio, thanks for trying to reproduce the error. Could you possibly run console.log(cookies) in your server component and post what the output is? I'm wondering if something in my setup could be altering the structure of these cookies,
many thanks in advance

@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 8, 2024
@michaelhlf
Copy link
Author

update: after stumbling across this ticket #13413, I realised my authConfig object being used to initialise the server runner was being exported from a module with the 'use client' directive. After moving the definition of this auth object to another server module, the issue is resolved!

@chrisbonifacio
Copy link
Member

Ah, nice find! Thank you for sharing the solution 🙏

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Oct 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Gen 2 Issues related to Gen 2 Amplify projects GraphQL Related to GraphQL API issues not-reproducible Not able to reproduce the issue
Projects
None yet
Development

No branches or pull requests

2 participants