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

Sometimes Amplify tokens get multiplied #14007

Open
3 tasks done
didemkkaslan opened this issue Nov 13, 2024 · 30 comments
Open
3 tasks done

Sometimes Amplify tokens get multiplied #14007

didemkkaslan opened this issue Nov 13, 2024 · 30 comments
Assignees
Labels
Auth Related to Auth components/category Next.js question General question

Comments

@didemkkaslan
Copy link

didemkkaslan commented Nov 13, 2024

Before opening, please confirm:

JavaScript Framework

Next.js

Amplify APIs

Authentication

Amplify Version

v6

Amplify Categories

auth

Backend

Amplify CLI

Environment information

# Put output below this line
  System:
    OS: macOS 14.7.1
    CPU: (8) arm64 Apple M2
    Memory: 156.86 MB / 8.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 18.16.0 - /usr/local/bin/node
    Yarn: 1.22.22 - /opt/homebrew/bin/yarn
    npm: 9.5.1 - /usr/local/bin/npm
    pnpm: 9.12.2 - /usr/local/bin/pnpm
    bun: 1.0.2 - ~/.bun/bin/bun
  Browsers:
    Chrome: 130.0.6723.117
    Safari: 18.1
  npmPackages:
    @amplitude/analytics-browser: ^2.3.3 => 2.11.1 
    @ampproject/toolbox-optimizer:  undefined ()
    @ant-design/cssinjs: 1.20.0 => 1.20.0 (1.21.1)
    @ant-design/icons: ^5.2.6 => 5.4.0 
    @ant-design/plots: 1.2.5 => 1.2.5 
    @aws-amplify/adapter-nextjs: ^1.2.26 => 1.2.23 
    @aws-amplify/adapter-nextjs/api:  undefined ()
    @aws-amplify/adapter-nextjs/data:  undefined ()
    @azure/msal-browser: ^3.24.0 => 3.24.0 
    @azure/msal-react: ^2.1.0 => 2.1.0 
    @babel/core:  undefined ()
    @babel/runtime:  7.22.5 
    @ckeditor/ckeditor5-build-classic: ^43.3.0 => 43.3.0 
    @ckeditor/ckeditor5-react: ^9.3.1 => 9.3.1 
    @edge-runtime/cookies:  5.0.0 
    @edge-runtime/ponyfill:  3.0.0 
    @edge-runtime/primitives:  5.0.0 
    @graphql-codegen/cli: ^5.0.3 => 5.0.3 
    @graphql-codegen/client-preset: ^4.4.0 => 4.4.0 
    @graphql-codegen/introspection: ^4.0.3 => 4.0.3 
    @hapi/accept:  undefined ()
    @mantine/hooks: ^7.1.5 => 7.12.2 
    @microsoft/teams-js: ^2.19.0 => 2.28.0 
    @mswjs/interceptors:  undefined ()
    @napi-rs/triples:  undefined ()
    @next/bundle-analyzer: 15.0.2 => 15.0.2 
    @next/font:  undefined ()
    @opentelemetry/api:  undefined ()
    @react-pdf/renderer: ^3.1.13 => 3.4.4 
    @tailwindcss/typography: ^0.5.10 => 0.5.15 
    @tanstack/query-codemods:  undefined ()
    @tanstack/react-query: ^5.0.5 => 5.54.1 
    @tanstack/react-query-devtools: ^5.8.9 => 5.54.1 
    @testing-library/jest-dom: ^6.1.4 => 6.5.0 
    @testing-library/react: ^14.0.0 => 14.3.1 
    @testing-library/user-event: ^14.5.1 => 14.5.2 
    @types/jest: ^29.5.6 => 29.5.12 
    @types/lodash: ^4.14.200 => 4.17.7 
    @types/mixpanel-browser: ^2.47.4 => 2.50.0 
    @types/node: ^20.8.8 => 20.16.5 (22.5.4)
    @types/react: ^18.3.12 => 18.3.12 
    @types/react-dom: ^18.3.1 => 18.3.1 
    @types/react-google-recaptcha: ^2.1.7 => 2.1.9 
    @types/react-highlight-words: ^0.16.6 => 0.16.7 
    @types/uuid: ^9.0.7 => 9.0.8 
    @typescript-eslint/eslint-plugin: ^8.13.0 => 8.13.0 
    @typescript-eslint/parser: ^8.13.0 => 8.13.0 
    @vercel/nft:  undefined ()
    @vercel/og:  0.6.3 
    acorn:  undefined ()
    amphtml-validator:  undefined ()
    anser:  undefined ()
    antd: 5.17.0 => 5.17.0 
    apexcharts: ^3.44.0 => 3.53.0 
    arg:  undefined ()
    assert:  undefined ()
    async-retry:  undefined ()
    async-sema:  undefined ()
    autoprefixer: ^10.4.16 => 10.4.20 
    aws-amplify: ^6.8.0 => 6.6.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 ()
    aws-rum-web: ^1.15.0 => 1.19.0 
    axios: ^1.5.1 => 1.7.7 
    babel-packages:  undefined ()
    browserify-zlib:  undefined ()
    browserslist:  undefined ()
    buffer:  undefined ()
    bytes:  undefined ()
    ci-info:  undefined ()
    cli-select:  undefined ()
    client-only:  0.0.1 
    clsx: ^2.0.0 => 2.1.1 
    commander:  undefined ()
    comment-json:  undefined ()
    compression:  undefined ()
    conf:  undefined ()
    constants-browserify:  undefined ()
    content-disposition:  undefined ()
    content-type:  undefined ()
    cookie:  undefined ()
    cookies-next: ^4.3.0 => 4.3.0 
    cross-spawn:  undefined ()
    crypto-browserify:  undefined ()
    css.escape:  undefined ()
    data-uri-to-buffer:  undefined ()
    dayjs: ^1.11.10 => 1.11.13 
    debug:  undefined ()
    devalue:  undefined ()
    docx: ^8.5.0 => 8.5.0 
    domain-browser:  undefined ()
    edge-runtime:  undefined ()
    eslint: ^8.52.0 => 8.57.0 
    eslint-config-airbnb: ^19.0.4 => 19.0.4 
    eslint-config-airbnb-typescript: ^17.1.0 => 17.1.0 
    eslint-config-next: 15.0.2 => 15.0.2 
    eslint-config-prettier: ^9.0.0 => 9.1.0 
    eslint-plugin-i18next: ^6.0.3 => 6.0.9 
    eslint-plugin-import: ^2.29.0 => 2.30.0 (2.31.0)
    eslint-plugin-jest: ^27.4.3 => 27.9.0 
    eslint-plugin-jest-dom: ^5.1.0 => 5.4.0 
    eslint-plugin-jsx-a11y: ^6.7.1 => 6.10.0 (6.10.2)
    eslint-plugin-react: ^7.33.2 => 7.35.2 (7.37.2)
    eslint-plugin-testing-library: ^6.1.2 => 6.3.0 
    events:  undefined ()
    find-cache-dir:  undefined ()
    find-up:  undefined ()
    framer-motion: ^11.11.11 => 11.11.11 
    fresh:  undefined ()
    get-orientation:  undefined ()
    glob:  undefined ()
    graphql: ^16.9.0 => 16.9.0 (15.8.0)
    graphql-request: 6.1.0 => 6.1.0 
    gzip-size:  undefined ()
    http-proxy:  undefined ()
    http-proxy-agent:  undefined ()
    https-browserify:  undefined ()
    https-proxy-agent:  undefined ()
    husky: ^8.0.3 => 8.0.3 
    i18next: ^23.7.15 => 23.14.0 
    i18next-chained-backend: ^4.5.0 => 4.6.2 
    i18next-http-backend: ^2.2.2 => 2.6.1 
    i18next-localstorage-backend: ^4.2.0 => 4.2.0 
    icss-utils:  undefined ()
    ignore-loader:  undefined ()
    image-size:  undefined ()
    is-animated:  undefined ()
    is-docker:  undefined ()
    is-wsl:  undefined ()
    jest: ^29.7.0 => 29.7.0 
    jest-environment-jsdom: ^29.7.0 => 29.7.0 
    jest-worker:  undefined ()
    json5:  undefined ()
    jsonwebtoken:  undefined ()
    jwt-decode: ^3.1.2 => 3.1.2 
    loader-runner:  undefined ()
    loader-utils:  undefined ()
    lodash: ^4.17.21 => 4.17.21 
    lodash.curry:  undefined ()
    lru-cache:  undefined ()
    mini-css-extract-plugin:  undefined ()
    mixpanel-browser: ^2.47.0 => 2.55.1 
    nanoid:  undefined ()
    native-url:  undefined ()
    neo-async:  undefined ()
    next: 14.2.16 => 14.2.16 
    next-i18next: ^15.1.2 => 15.3.1 
    next-i18next-create-client:  undefined ()
    next-seo: ^6.1.0 => 6.6.0 
    node-fetch:  undefined ()
    node-html-parser:  undefined ()
    ora:  undefined ()
    os-browserify:  undefined ()
    p-limit:  undefined ()
    path-browserify:  undefined ()
    picomatch:  undefined ()
    platform:  undefined ()
    postcss: ^8.4.31 => 8.4.45 (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.0.3 => 3.3.3 
    prettier-plugin-tailwindcss: ^0.5.6 => 0.5.14 
    process:  undefined ()
    punycode:  undefined ()
    querystring-es3:  undefined ()
    raw-body:  undefined ()
    react: ^18.3.1 => 18.3.1 
    react-apexcharts: ^1.4.1 => 1.4.1 
    react-builtin:  undefined ()
    react-dom: ^18.3.1 => 18.3.1 
    react-dom-builtin:  undefined ()
    react-dom-experimental-builtin:  undefined ()
    react-error-boundary: ^4.0.13 => 4.0.13 
    react-experimental-builtin:  undefined ()
    react-google-recaptcha: ^3.1.0 => 3.1.0 
    react-highlight-words: ^0.20.0 => 0.20.0 
    react-i18next: ^14.0.0 => 14.1.3 
    react-icons: ^4.11.0 => 4.12.0 
    react-infinite-scroll-component: ^6.1.0 => 6.1.0 
    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.32.6 => 0.32.6 
    shell-quote:  undefined ()
    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 ()
    tailwind-merge: ^1.14.0 => 1.14.0 
    tailwindcss: ^3.3.3 => 3.4.10 
    tar:  undefined ()
    terser:  undefined ()
    text-table:  undefined ()
    timers-browserify:  undefined ()
    tty-browserify:  undefined ()
    typescript: ^5.2.2 => 5.5.4 
    ua-parser-js:  undefined ()
    unistore:  undefined ()
    usehooks-ts: ^2.9.1 => 2.16.0 
    util:  undefined ()
    uuid: ^9.0.1 => 9.0.1 
    vm-browserify:  undefined ()
    watchpack:  undefined ()
    web-vitals:  undefined ()
    webpack:  undefined ()
    webpack-sources:  undefined ()
    ws:  undefined ()
    zod: ^3.22.4 => 3.23.8 ()
    zustand: ^4.5.2 => 4.5.5 
  npmGlobalPackages:
    @aws-amplify/cli: 12.12.6
    corepack: 0.17.0
    eas-cli: 12.4.1
    expo-cli: 6.3.10
    npm: 9.5.1
    pnpm: 9.12.2
    turbo: 2.1.2


Describe the bug

Hello:), I can rarely reproduce this but here is a user reporting it:

I was at home fully. And I used the platform let’s say 45 mins ago or so. Then I clicked on “Report Link” in my email and that’s when I saw this browser output.

Email link is just a redirection to meeting detail page. Since token sizes are big we also get request header too large error when they got multiplied and page crash

image (4)
image (5)

We use a custom cookie storage where I tried to prevent token multiplication error:

import amplifyconfig from '../../amplifyconfiguration.json';
import { deduplicatedFetchAuthSession } from '../lib/deduplicatedFetchAuthSession';
import { Amplify } from 'aws-amplify';
import { parseAmplifyConfig } from 'aws-amplify/utils';
import { createKeyValueStorageFromCookieStorageAdapter } from 'aws-amplify/adapter-core';
import { deleteCookie, getCookie, setCookie, getCookies } from 'cookies-next';
import { cognitoUserPoolsTokenProvider } from 'aws-amplify/auth/cognito';
import { OptionsType } from 'cookies-next/lib/types';

const amplifyConfig = parseAmplifyConfig(amplifyconfig);

const cookieOptions: OptionsType =
  process.env.NEXT_PUBLIC_ENV === 'msteams'
    ? {
        domain: 'tab.app.spiky.ai' as string,
        sameSite: 'none' as 'lax' | 'strict' | 'none',
        secure: true,
      }
    : {};

const keyValueStorage = createKeyValueStorageFromCookieStorageAdapter({
  get(name) {
    const value = getCookie(name, cookieOptions);
    return { name, value };
  },
  getAll() {
    const cookies = getCookies(cookieOptions);
    return Object.keys(cookies).map((name) => ({ name, value: cookies[name] }));
  },
  set(name, value) {
    const cookieAlreadyExists = getCookie(name, cookieOptions);

    if (cookieAlreadyExists) {
      console.log(
        'cookieAlreadyExists so Im deleting it and setting new one',
        cookieAlreadyExists,
      );
      deleteCookie(name, cookieOptions);
    }
    setCookie(name, value, cookieOptions);
  },
  delete(name) {
    deleteCookie(name, cookieOptions);
  },
});

export const getAuthToken = async () => {
  const session = await deduplicatedFetchAuthSession();
  return session.tokens?.idToken?.toString() as string;
};

export function configureAmplify() {
  Amplify.configure(
    {
      ...amplifyConfig,
      Auth: {
        ...amplifyConfig.Auth,
        Cognito: {
          ...amplifyConfig.Auth?.Cognito,
          identityPoolId:
            process.env.NEXT_PUBLIC_PLATFORM_COGNITO_IDENTITY_POOL_ID!,
          userPoolId: process.env.NEXT_PUBLIC_PLATFORM_COGNITO_USER_POOL_ID!,
          userPoolClientId:
            process.env.NEXT_PUBLIC_PLATFORM_COGNITO_USER_POOL_WEB_CLIENT_ID!,
        },
      },
      API: {
        ...amplifyConfig.API,
        REST: {
          ...amplifyConfig.API?.REST,
          PlatformCorePublicRestApi: {
            endpoint: `${process.env.NEXT_PUBLIC_PLATFORM_CORE_REST_API_ENDPOINT}/public`,
          },
          PlatformCoreRestApi: {
            endpoint: `${process.env.NEXT_PUBLIC_PLATFORM_CORE_REST_API_ENDPOINT}/platform`,
          },
          PlatformCoreCalendarRestApi: {
            endpoint: `${process.env.NEXT_PUBLIC_PLATFORM_CORE_REST_API_ENDPOINT}/calendar-v2`,
          },
          PlatformCoreTeamsRestApi: {
            endpoint: `${process.env.NEXT_PUBLIC_PLATFORM_CORE_REST_API_TEAMS_ENDPOINT}`,
          },
          PlatformIntegrationRestApi: {
            endpoint: `${process.env.NEXT_PUBLIC_PLATFORM_INTEGRATION_REST_API_ENDPOINT}`,
          },
          PlatformRestApi: {
            endpoint: `${process.env.NEXT_PUBLIC_PLATFORM_REST_API_ENDPOINT}`,
          },
        },
      },
    },
    {
      ssr: true,
      API: {
        REST: {
          headers: async () => ({
            Authorization: `Bearer ${await getAuthToken()}`,
          }),
        },
      },
    },
  );

  cognitoUserPoolsTokenProvider.setKeyValueStorage(keyValueStorage);
}

Expected behavior

Tokens shouldn't get multiplied

Reproduction steps

Not really sure why and when this happens but it usually happens when a tab is left open 45mins or so

Code Snippet

// Put your code below this line.

Log output

// Put your logs below this line


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

@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 Nov 13, 2024
@cwomack cwomack self-assigned this Nov 13, 2024
@cwomack cwomack added Auth Related to Auth components/category question General question and removed pending-triage Issue is pending triage labels Nov 13, 2024
@didemkkaslan
Copy link
Author

Added another screenshot, this time there is smth like username I'm not sure maybe its helpful
CleanShot 2024-11-13 at 20 12 41@2x

@cwomack
Copy link
Member

cwomack commented Nov 13, 2024

Hello, @didemkkaslan 👋. Can you help me understand how you're looking to use that OptionsType import from the cookies-next library? It looks like you're setting secure: true within your cookieOptions, but in the screenshots the cookies don't look to be marked with the secure ✅ in the dev tools column.

Is this intentional? Just want to make sure we aren't missing any intended functionality here.

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

Sure, we also have msteams tab app on the domain tab.app.spiky.ai. Its an iframe on msteams app so we weren't able to login there with the default local storage method amplify uses. So this is actually why we implemented custom cookie storage. But we get the duplicate token error when NEXT_PUBLIC_ENV is not msteams. so cookie options are empty in this case. I'm not sure if I'm missing smth

@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 Nov 13, 2024
@cwomack
Copy link
Member

cwomack commented Nov 15, 2024

@didemkkaslan, I'm a little confused here. If you're using Next.JS, the default implementation and usage for storage should be cookieStorage. Is there a reason why you were trying to utilize localStorage (and then needed to implement a custom cookie storage solution)?

If the custom cookie storage is needed, you'll need to make sure you're persisting the cookie only if it's not a duplicate. This PR is an example of how we had to implement this fix for our cookie storage that is used out of the box with Next.JS.

@cwomack cwomack added pending-community-response Issue is pending a response from the author or community. and removed pending-maintainer-response Issue is pending a response from the Amplify team. labels Nov 15, 2024
@didemkkaslan
Copy link
Author

didemkkaslan commented Nov 18, 2024

Hello @cwomack sorry for the confusion, I'm not really sure if I need to implement custom cookie storage here. The issue is we have a microsoft teams tab app where tab.app.spiky.ai domain is used in an iframe and its kind of embedded. It looks at the tab.app.spiky.ai domain and I need the amplify tokens to be stored there to be able to authenticate.
And we also have our main app at app.spiky.ai

CleanShot 2024-11-18 at 17 56 35@2x

Is below implementation the way to go?

  const isMSTeams = process.env.NEXT_PUBLIC_ENV === 'msteams';
  cognitoUserPoolsTokenProvider.setKeyValueStorage(
    new CookieStorage({
      ...(isMSTeams && {
        domain: 'tab.app.spiky.ai',
      }),
      secure: true,
      path: '/',
      sameSite: isMSTeams ? 'none' : 'lax',
      expires: 365,
    }),
  );
}

I'm a bit confused shouldn't I be doing any customizations?

@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 Nov 18, 2024
@didemkkaslan
Copy link
Author

didemkkaslan commented Nov 20, 2024

I've changed the cookie implementation to this:

  const isMSTeams = process.env.NEXT_PUBLIC_ENV === 'msteams';
  cognitoUserPoolsTokenProvider.setKeyValueStorage(
    new CookieStorage({
      ...(isMSTeams && {
        domain: 'tab.app.spiky.ai',
      }),
      secure: true,
      path: '/',
      sameSite: isMSTeams ? 'none' : 'lax',
      expires: 365,
    }),
  );

}

and the token duplication issue still happens. After many token refresh events deduplicatedFetchAuthSession isn't able to get the tokens and I believe after that tokens get multiplied

import { AuthSession, fetchAuthSession } from 'aws-amplify/auth';

/**
 * This function creates a singleton that fetches the current session. This is necessary
 * because the Amplify fetchAuthSession method does not de-duplicate credential requests to
 * the Cognito server. That means that duplicate requests will be made to the server if
 * multiple components call fetchAuthSession concurrently before the internal credential
 * cache has been populated. This singleton ensures that only one request is made at a time.
 *
 * A feature request has been made to the Amplify team to add de-duplication
 * to the fetchAuthSession method. If that feature is added, this layer ofz
 * abstraction can be removed.
 *
 * @see https://github.com/aws-amplify/amplify-js/issues/13499
 */
function createDeduplicatedFetchAuthSessionSingleton() {
  let pendingRequest: Promise<AuthSession> | null = null;

  console.log('pendingRequest', pendingRequest);

  return () => {
    if (!pendingRequest) {
      pendingRequest = new Promise((resolve, reject) => {
        (async () => {
          try {
            const response = await fetchAuthSession();
            resolve(response);
          } catch (error) {
            reject(error);
          } finally {
            pendingRequest = null;
          }
        })();
      });
    }

    return pendingRequest;
  };
}

export const deduplicatedFetchAuthSession =
createDeduplicatedFetchAuthSessionSingleton();

CleanShot 2024-11-20 at 11 24 03@2x

CleanShot 2024-11-20 at 11 21 17@2x

CleanShot 2024-11-20 at 11 24 59@2x
CleanShot 2024-11-20 at 11 27 01@2x

@cwomack
Copy link
Member

cwomack commented Nov 20, 2024

@didemkkaslan, is there any logic to check when setting a cookie if they are the same name/user? And are you trying to set different usernames in the cookies or have multiple users during a session? I only ask these because it looks like there are difference usernames in one of the screenshots you provided that were still being stored.

Also, we don't recommend changing the value for sameSite as a best practice for security. Was there a need for changing this in your cookieOptions? Ideally, you'd use our generic adapter when implementing your own cookie management solution.

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

Hello, no there isn't any logic it was just this code:

  const isMSTeams = process.env.NEXT_PUBLIC_ENV === 'msteams';
  cognitoUserPoolsTokenProvider.setKeyValueStorage(
    new CookieStorage({
      ...(isMSTeams && {
        domain: 'tab.app.spiky.ai',
      }),
      secure: true,
      path: '/',
      sameSite: isMSTeams ? 'none' : 'lax',
      expires: 365,
    }),
  );

So I'm not even touching how cookies are being added, deleted just some cookie options.

I also call fetchAuthSession from server inside getServerSideProps to access some user data. Can this be the reason? Like I saw couple of comments about server sets cookies too. When I check the duplicate cookie signInDetails then I see the same user actually: didem@spiky.ai

idtoken content also belongs to the same user. I'm not sure what that username is in the cookie name.

Today I converted cookie storage implementation to this:

cognitoUserPoolsTokenProvider.setKeyValueStorage(new CookieStorage());

Couldn't reproduce the token multiplication yet

@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 Nov 20, 2024
@cwomack
Copy link
Member

cwomack commented Nov 21, 2024

@didemkkaslan, appreciate your trying the default cookie storage and great to hear that the token multiplication is no longer happening! After implementing it, is there any other issues or is the app working as expected now? Maybe we can follow up in a week or so to see if it occurs again.

@cwomack cwomack added pending-community-response Issue is pending a response from the author or community. and removed pending-maintainer-response Issue is pending a response from the Amplify team. labels Nov 21, 2024
@didemkkaslan
Copy link
Author

Thanks for your help. I'm only using Amplify's cookie storage—nothing else. The issue doesn't always reproduce, but it tends to happen when multiple tabs are left inactive for about a day, followed by an ERROR_CONNECTION_RESET message.

This current implementation is causing the bug, but when I remove the environment configurations, the API calls become unauthorized. Btw problem occurs both with deduplicatedFetchAuthSession and the default fetchAuthSession

import amplifyconfig from '../../amplifyconfiguration.json';
import { deduplicatedFetchAuthSession } from '../lib/deduplicatedFetchAuthSession';
import { Amplify } from 'aws-amplify';
import { CookieStorage, parseAmplifyConfig } from 'aws-amplify/utils';
import { cognitoUserPoolsTokenProvider } from 'aws-amplify/auth/cognito';

const amplifyConfig = parseAmplifyConfig(amplifyconfig);

export const getAuthToken = async () => {
  const session = await deduplicatedFetchAuthSession();
  return session.tokens?.idToken?.toString() as string;
};

export function configureAmplify() {
  Amplify.configure(
    {
      ...amplifyConfig,
      Auth: {
        ...amplifyConfig.Auth,
        Cognito: {
          ...amplifyConfig.Auth?.Cognito,
          identityPoolId:
            process.env.NEXT_PUBLIC_PLATFORM_COGNITO_IDENTITY_POOL_ID!,
          userPoolId: process.env.NEXT_PUBLIC_PLATFORM_COGNITO_USER_POOL_ID!,
          userPoolClientId:
            process.env.NEXT_PUBLIC_PLATFORM_COGNITO_USER_POOL_WEB_CLIENT_ID!,
        },
      },
      API: {
        ...amplifyConfig.API,
        REST: {
          ...amplifyConfig.API?.REST,
          PlatformCorePublicRestApi: {
            endpoint: `${process.env.NEXT_PUBLIC_PLATFORM_CORE_REST_API_ENDPOINT}/public`,
          },
          PlatformCoreRestApi: {
            endpoint: `${process.env.NEXT_PUBLIC_PLATFORM_CORE_REST_API_ENDPOINT}/platform`,
          },
          PlatformCoreCalendarRestApi: {
            endpoint: `${process.env.NEXT_PUBLIC_PLATFORM_CORE_REST_API_ENDPOINT}/calendar-v2`,
          },
          PlatformCoreTeamsRestApi: {
            endpoint: `${process.env.NEXT_PUBLIC_PLATFORM_CORE_REST_API_TEAMS_ENDPOINT}`,
          },
          PlatformIntegrationRestApi: {
            endpoint: `${process.env.NEXT_PUBLIC_PLATFORM_INTEGRATION_REST_API_ENDPOINT}`,
          },
          PlatformRestApi: {
            endpoint: `${process.env.NEXT_PUBLIC_PLATFORM_REST_API_ENDPOINT}`,
          },
        },
      },
    },
    {
      ssr: true,
      API: {
        REST: {
          headers: async () => ({
            Authorization: `Bearer ${await getAuthToken()}`,
          }),
        },
      },
    },
  );

  cognitoUserPoolsTokenProvider.setKeyValueStorage(new CookieStorage());
}
import { AuthSession, fetchAuthSession } from 'aws-amplify/auth';

/**
 * This function creates a singleton that fetches the current session. This is necessary
 * because the Amplify fetchAuthSession method does not de-duplicate credential requests to
 * the Cognito server. That means that duplicate requests will be made to the server if
 * multiple components call fetchAuthSession concurrently before the internal credential
 * cache has been populated. This singleton ensures that only one request is made at a time.
 *
 * A feature request has been made to the Amplify team to add de-duplication
 * to the fetchAuthSession method. If that feature is added, this layer ofz
 * abstraction can be removed.
 *
 * @see https://github.com/aws-amplify/amplify-js/issues/13499
 */
function createDeduplicatedFetchAuthSessionSingleton() {
  let pendingRequest: Promise<AuthSession> | null = null;

  console.log('pendingRequest', pendingRequest);

  return () => {
    if (!pendingRequest) {
      pendingRequest = new Promise((resolve, reject) => {
        (async () => {
          try {
            const response = await fetchAuthSession();
            resolve(response);
          } catch (error) {
            console.error('Error fetching auth session', error);

            reject(error);
          } finally {
            pendingRequest = null;
          }
        })();
      });
    }

    return pendingRequest;
  };
}

export const deduplicatedFetchAuthSession =
  createDeduplicatedFetchAuthSessionSingleton();

We also call fetchAuthSession in getServerSideProps

import config from '@/amplifyconfiguration.json';
import { createServerRunner } from '@aws-amplify/adapter-nextjs';

export const { runWithAmplifyServerContext } = createServerRunner({
  config,
});
export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const { user, meeting } = await runWithAmplifyServerContext({
    nextServerContext: { request: ctx.req, response: ctx.res },
    operation: async (contextSpec) => {
      const session = await fetchAuthSession(contextSpec);
      const token = session.tokens?.idToken?.toString();

      try {
        const userRestOperation = get({
          apiName: APINames.PlatformCoreRestApi,
          path: '/people/me',
          options: {
            headers: {
              Authorization: `Bearer ${token}`,
            },
          },
        });
        const { body } = await userRestOperation.response;
        const userData = (await body.json()) as unknown as PeopleData;

        const meetingId = ctx.params?.meetingId as string;

        const meetingRestOperation = get({
          apiName: APINames.PlatformCoreRestApi,
          path: `/meeting-reports/${meetingId}`,
          options: {
            headers: {
              Authorization: `Bearer ${token}`,
            },
          },
        });

        const meetingResponse = await meetingRestOperation.response;
        const meetingData =
          (await meetingResponse.body.json()) as unknown as MeetingData;

        return { user: userData, meeting: meetingData };
      } catch (e) {
        console.error(e);
        return { user: null, meetingData: null };
      }
    },
  });

//....



  return {
    props: {},
  };
};

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 3, 2024
@HuiSF
Copy link
Member

HuiSF commented Dec 3, 2024

Thanks for providing the sample code I couldn't however isolate the issue by just looking at the code... A few more questions for you to help us understand the situation better:

  • What's the purpose of using ENV vars to override fields (such as userPoolId etc.) of the configuration object imported from amplifyconfiguration.json on the client side?
  • When accessing the route that invokes getServerSideProps() in the 3rd block of your code, does your app also calls the client-side fetchAuthSession at the same time? (My recommendation is to avoid refresh session for the same user on both client-side and server-side at the same time, as they are performed in two completely separate environments, race condition may happen and lead to unexpected results.)

Quick suggestions:

  1. Please remove this line as it's overrides ssr: true
cognitoUserPoolsTokenProvider.setKeyValueStorage(new CookieStorage());
  1. Looking at the 3rd block of your code, it looks like you are using the client side get API, please use the server one export from aws-amplify/api/server.

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 3, 2024
@HuiSF
Copy link
Member

HuiSF commented Dec 3, 2024

Also you mentioned about ERROR_CONNECTION_RESET looking at the screenshot you provided previously they are thrown against the Cognito service endpoint, this means at the moment it attempted refresh tokens on the client-side your browser was still offline.

We found an issue for versions < 6.6.7 that the library may wrongly clear tokens on NetworkError which has been fixed since v6.6.7. I noticed in your OP your aws-amplify version was 6.6.4, could you try to upgrade to the latest version to ensure this unexpected behavior from happening?

@didemkkaslan
Copy link
Author

I've also upgraded long time ago to these versions:

"aws-amplify": "^6.8.0",
"@aws-amplify/adapter-nextjs": "^1.2.26",

When accessing the route that invokes getServerSideProps() in the 3rd block of your code, does your app also calls the client-side fetchAuthSession at the same time?

  • Its calling fetchAuthSession from server-side
    import { fetchAuthSession } from 'aws-amplify/auth/server';

Looking at the 3rd block of your code, it looks like you are using the client side get API, please use the server one export from aws-amplify/api/server.

  • Ups, updated them to use import { get } from 'aws-amplify/api/server';

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 3, 2024
@didemkkaslan
Copy link
Author

I've also removed
cognitoUserPoolsTokenProvider.setKeyValueStorage(new CookieStorage()); but does that mean now cookies will be stored in localstorage?

@didemkkaslan
Copy link
Author

What's the purpose of using ENV vars to override fields (such as userPoolId etc.) of the configuration object imported from amplifyconfiguration.json on the client side?

  • Not really sure about that it was that way when we were using v5 and I just made them work with v6 might be wrong

@HuiSF
Copy link
Member

HuiSF commented Dec 4, 2024

I've also removed cognitoUserPoolsTokenProvider.setKeyValueStorage(new CookieStorage()); but does that mean now cookies will be stored in localstorage?

Amplify.configure(config, { ssr: true }) instructs the library to use cookie store as it's stated here.

Not really sure about that it was that way when we were using v5 and I just made them work with v6 might be wrong

If amplifyconfiguration.json contains all required the fields with correct values, please try to simplify your code - so we have a better chance to isolate the issue when it happens.

When accessing the route that invokes getServerSideProps() in the 3rd block of your code, does your app also calls the client-side fetchAuthSession at the same time?

Its calling fetchAuthSession from server-side
import { fetchAuthSession } from 'aws-amplify/auth/server';

With this question, I'm curious when you accessing this route where the issue has occurred, do you have any business logic that calls Amplify APIs on both client side and server side that may trigger token refresh. E.g. you have logic running on the client-side calling the client-side fetchAuthSession API, and this route also calls getServerSideProps() on the server side also calls the server-side fetchAuthSession, and both of them may trigger token refresh since user session has expired.

@github-actions github-actions bot added pending-maintainer-response Issue is pending a response from the Amplify team. and removed pending-maintainer-response Issue is pending a response from the Amplify team. labels Dec 4, 2024
@didemkkaslan
Copy link
Author

didemkkaslan commented Dec 4, 2024

With this question, I'm curious when you accessing this route where the issue has occurred, do you have any business logic that calls Amplify APIs on both client side and server side that may trigger token refresh. E.g. you have logic running on the client-side calling the client-side fetchAuthSession API, and this route also calls getServerSideProps() on the server side also calls the server-side fetchAuthSession, and both of them may trigger token refresh since user session has expired.

  • On the client I'm using fetchAuthSession to add Authorization header token
 API: {
        REST: {
          headers: async () => ({
            Authorization: `Bearer ${await getAuthToken()}`, // gets idtoken using fetchAuthSession client side
          }),
        },
      },

And on the server I also need to check user permission so I'm calling the serverside fetchAuthSession is there any alternative because we are redirecting the user if he/she doesnt have the permission to see the page

@HuiSF
Copy link
Member

HuiSF commented Dec 4, 2024

Hi @didemkkaslan regarding the my comment calling fetchAuthSession on both client side and sever side may not be impactful for your case, as the client side fetchAuthSession should be guaranteed to run after the fetchAuthSession called in getServerSideProps in the server side.

Could you verify whether the issue happens after the suggested clean ups?

In addition, curious why do you choose to use idToken instead of accessToken, do you need to read extra information from the idToken?

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 4, 2024
@didemkkaslan
Copy link
Author

I've merged the suggested cleanups of using server-side get function and removing the extra line of cognitoUserPoolsTokenProvider.setKeyValueStorage(new CookieStorage());. Will update here if the issue occurs again, since it is not reproducable all the time I think i need to give it some time. thanks for all the help.

Not reallly we are actually thinking about using accessToken instead of idToken these days. Do you recommend putting scopes in the idtoken or accesstoken? We have many scopes for user permission stuff and cookie sizes are really large

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 4, 2024
@HuiSF
Copy link
Member

HuiSF commented Dec 4, 2024

In general, access token is always recommended to be used as authorization information instead of the ID token. With Amazon Congnito for example, you can revoke tokens, but the tokens can actually be revoked are the access token and refresh token.

I believe you are able to customize access token today with adding custom scopes, please see this AWS Security Blog for more details (please review the "consideration and best practices" section carefully).

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 4, 2024
@didemkkaslan
Copy link
Author

Hi, unfortunately after the latest cleanups issue is still continuing. One of our users got 431 request headers too large error. Can this be related to token revocation mechanism? I believe backend team told us they weren't revoking tokens or is it completely unrelated

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 6, 2024
@HuiSF
Copy link
Member

HuiSF commented Dec 6, 2024

Hi @didemkkaslan

Can this be related to token revocation mechanism?

If the CognitoIdentityServiceProvider.<user-pool-client-id>.<username>.LastAuthUser cookie wasn't messed up - when the token has been revoked somewhere else, when the Amplify JS library attempts refresh token it will receive token has been revoked error, in this case the library clears cached tokens.

One of our users got 431 request headers too large error.

Has this user had the same issue before the cleanups? If cookies are previously duplicated - they stay in cookie store until they expire. As the cookie names are messed up, the library won't able to detect them and clear. At this point the end user may need to clear cookie store manually.

I'm really curious what's happening to cause this. If you are still able to reproduce on your end after the cleanups, can you generate a HAR file from the network traffic around the time you reproduce it? I will do some digging.

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 6, 2024
@didemkkaslan
Copy link
Author

I've asked the team to generate a HAR file when it happens, I will also try to reproduce it myself

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 6, 2024
@HuiSF HuiSF removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 6, 2024
@didemkkaslan
Copy link
Author

We were able to reproduce the issue and below is the har file content. By the way the preserve log checkbox wasn't checked not sure if its still helpful. Thanks bunch :)

{
"log": {
"version": "1.2",
"creator": {
"name": "WebInspector",
"version": "537.36"
},
"pages": [],
"entries": [
{
"_initiator": {
"type": "other"
},
"_priority": "VeryHigh",
"_resourceType": "document",
"cache": {},
"request": {
"method": "GET",
"url": "https://app.spiky.ai/meetings",
"httpVersion": "h3",
"headers": [
{
"name": ":authority",
"value": "app.spiky.ai"
},
{
"name": ":method",
"value": "GET"
},
{
"name": ":path",
"value": "/meetings"
},
{
"name": ":scheme",
"value": "https"
},
{
"name": "accept",
"value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.7"
},
{
"name": "accept-encoding",
"value": "gzip, deflate, br, zstd"
},
{
"name": "accept-language",
"value": "en-US,en;q=0.9"
},
{
"name": "cache-control",
"value": "no-cache"
},
{
"name": "pragma",
"value": "no-cache"
},
{
"name": "priority",
"value": "u=0, i"
},
{
"name": "sec-ch-ua",
"value": ""Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24""
},
{
"name": "sec-ch-ua-mobile",
"value": "?1"
},
{
"name": "sec-ch-ua-platform",
"value": ""Android""
},
{
"name": "sec-fetch-dest",
"value": "document"
},
{
"name": "sec-fetch-mode",
"value": "navigate"
},
{
"name": "sec-fetch-site",
"value": "none"
},
{
"name": "sec-fetch-user",
"value": "?1"
},
{
"name": "upgrade-insecure-requests",
"value": "1"
},
{
"name": "user-agent",
"value": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36"
}
],
"queryString": [],
"cookies": [],
"headersSize": -1,
"bodySize": 0
},
"response": {
"status": 431,
"statusText": "",
"httpVersion": "h3",
"headers": [
{
"name": "alt-svc",
"value": "h3=":443"; ma=86400"
},
{
"name": "content-length",
"value": "0"
},
{
"name": "date",
"value": "Sat, 14 Dec 2024 00:05:47 GMT"
},
{
"name": "via",
"value": "1.1 4da69145f7e1fe6a8bdcb04dc7af89e8.cloudfront.net (CloudFront)"
},
{
"name": "x-amz-cf-id",
"value": "COxWt5mIwmqNId5iuX8wErNyn1P_y70Y2oxaOMP8QIyLLmAs1U6zpw=="
},
{
"name": "x-amz-cf-pop",
"value": "BOS50-C2"
},
{
"name": "x-cache",
"value": "Error from cloudfront"
}
],
"cookies": [],
"content": {
"size": 0,
"mimeType": "text/plain"
},
"redirectURL": "",
"headersSize": -1,
"bodySize": -1,
"_transferSize": 206,
"_error": "net::ERR_HTTP_RESPONSE_CODE_FAILURE",
"_fetchedViaServiceWorker": false
},
"serverIPAddress": "13.249.190.48",
"startedDateTime": "2024-12-14T00:05:47.577Z",
"time": 330.21600016871093,
"timings": {
"blocked": 4.245000280410052,
"dns": 30.413999999999998,
"ssl": 14.490000000000002,
"connect": 45.097,
"send": 0.5789999999999935,
"wait": 246.9050000449717,
"receive": 2.975999843329191,
"_blocked_queueing": 1.6880002804100513,
"_workerStart": -1,
"_workerReady": -1,
"_workerFetchStart": -1,
"_workerRespondWithSettled": -1
}
}
]
}
}
Screenshot 2024-12-14 at 03 45 22

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 14, 2024
@pranavosu
Copy link
Member

Hi @didemkkaslan, thanks for providing the HAR file. I'll try to reproduce this as well.

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 16, 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

4 participants