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.44k stars 2.13k forks source link

Invalid login token. Token expired #13993

Closed didemkkaslan closed 1 week ago

didemkkaslan commented 1 week ago

Before opening, please confirm:

JavaScript Framework

Next.js

Amplify APIs

Authentication

Amplify Version

v6

Amplify Categories

auth

Backend

CDK

Environment information

``` # Put output below this line System: OS: macOS 14.7 CPU: (8) arm64 Apple M2 Memory: 83.34 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.0.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.23 => 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.6.4 => 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 Amplify team:

When the app is left running in the background for approximately 2-3 hours, I encounter the following error:

Screenshot 2024-11-08 at 14 33 52 Screenshot 2024-11-08 at 14 38 33 Screenshot 2024-11-08 at 14 39 18 Screenshot 2024-11-08 at 14 41 04

Here is the dedupedFetchAuthSession:

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();

I refetch scopes using fetchAuthSession for permissions stuff when a user signs in or a token refresh occurs.


/* eslint-disable consistent-return */
/* eslint-disable default-case */
import { PeopleData } from '@/people/types/PeopleData';
import getPersonName from '@/people/utils/getPersonName';
import { getCurrentUser } from '@/services/settings/userService';
import { deduplicatedFetchAuthSession } from '@/shared/lib/deduplicatedFetchAuthSession';
import { ONBOARDING_INTRODUCED_AT } from '@/shared/lib/getOnboardingStatus';
import { bootIntercom, shutdownIntercom } from '@/shared/lib/intercom';
import Route from '@/shared/routes';
import generateUserHash from '@/shared/services/generateUserHash';
import { useQueryClient } from '@tanstack/react-query';
import { message } from 'antd';
import { signOut } from 'aws-amplify/auth';
import { Hub } from 'aws-amplify/utils';
import { setCookie } from 'cookies-next';
import dayjs from 'dayjs';
import { useRouter } from 'next/router';
import React, { useEffect } from 'react';

interface IdentificationProps {
  children: React.ReactNode;
}

const sendUserDetailsToIntercom = async (user: PeopleData) => {
  shutdownIntercom();
  const userHash = await generateUserHash(user?.id);
  const username = getPersonName(user);
  bootIntercom({
    app_id: process.env.NEXT_PUBLIC_INTERCOM_APP_ID,
    name: username,
    email: user?.email,
    user_id: user?.id,
    user_hash: userHash,
    'Job Title': user?.title,
    Company: user?.company?.companyName,
    'Referral Source': user?.referralSource,
    'Role Level': user?.roleLevel,
    Department: user?.department,
    'Company Size': user?.company?.companySize,
    company: {
      id: user?.company?.id,
      name: user?.company?.companyName,
    },
  });
};

export default function Identification({ children }: IdentificationProps) {
  const queryClient = useQueryClient();
  const router = useRouter();

  const hubListenerCancelToken = Hub.listen('auth', async ({ payload }) => {
    console.log('auth event:', payload);
    switch (payload.event) {
      case 'signInWithRedirect': {
        break;
      }
      case 'signedIn': {
        const session = await deduplicatedFetchAuthSession();
        queryClient.setQueryData(
          ['scopes'],
          session?.tokens?.idToken?.payload?.scope?.split(' ') ?? [],
        );

        const user = await getCurrentUser();
        sendUserDetailsToIntercom(user);

        // Check whether the user has completed the onboarding
        const onboardingCompleted = user?.onboardingCompleted === true;
        const isAfterOnboardingIntroducedAt = dayjs(user?.createdAt).isAfter(
          ONBOARDING_INTRODUCED_AT,
        );
        const isRedirectToOnboarding =
          !onboardingCompleted && isAfterOnboardingIntroducedAt;

        if (isRedirectToOnboarding) {
          router.push('/onboarding/personal-info');
        } else {
          router.push('/meetings');
        }
        break;
      }

      case 'signedOut': {
        // Here we need to shutdown Intercom and boot it again to clear the user data when the user signs out
        shutdownIntercom();
        bootIntercom();

        break;
      }
      case 'tokenRefresh':
        console.log('auth tokens have been refreshed.');
        const session = await deduplicatedFetchAuthSession();
        queryClient.setQueryData(
          ['scopes'],
          session?.tokens?.idToken?.payload?.scope?.split(' ') ?? [],
        );
        break;
      case 'tokenRefresh_failure':
        console.log('failure while refreshing auth tokens.');
        signOut()
          .then(() => {
            setCookie('calendarModalShown', 'false', {
              maxAge: 90 * 24 * 60 * 60,
              path: '/',
            });
            queryClient.clear();
            router.push(Route.Login);
          })
          .catch((err) => {
            message.error(err.message);
          });
        break;

      case 'signInWithRedirect_failure':
        console.log('failure while trying to resolve signInWithRedirect API.');
        break;
      case 'customOAuthState':
        console.log('custom state returned from CognitoHosted UI');
        break;
    }
  });

  useEffect(() => {
    if (typeof window === 'undefined' || !window.Intercom) return;

    return () => {
      hubListenerCancelToken();
    };
  }, [hubListenerCancelToken]);

  return children;
}

Expected behavior

fetchAuthSession shouldn't cause any errors

Reproduction steps

Put the app running in background , wait good amount of time

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

cwomack commented 1 week ago

@didemkkaslan, thanks for opening this issue and sorry to hear you're running into this. Can you clarify if you only experienced this after a recent upgrade (I see you're using v6.6.4)? Also, can you share what your settings are the expiration of your access token, ID Token, and refresh tokens within your Cognito User Pool?

didemkkaslan commented 1 week ago

We occasionally have this error for a long time now but yes after we upgraded to amplify v6 and started to implement permission stuff based on the idtoken(scopes). Sometimes after a tokenRefresh_failure event user silently gots logged out ( tokens are being removed from cookie storage in this case ) I'm not sure if its related tho.

I've asked backend team and here is the info for tokens

access token expiry = 5 mins Id token expiry = 5 mins Refresh token expiry = 1 week

thanks bunch

ashika112 commented 1 week ago

@didemkkaslan I wanted to confirm that the behavior for Next JS works as expected as opposed to issue aws-amplify/amplify-ui#6057. The customer is not logged out after network error. Please note, the expected behavior was with default cookie storage implementation. If you have a custom implementation, it might need to handle error scenarios.

Screenshot 2024-11-11 at 11 19 16 AM

didemkkaslan commented 1 week ago

Hello @ashika112 the behaviour works as expected in version 6.8.0 tokens are not being deleted in this version. I was having this behaviour with 6.6.4 looks like my issue is solved with version upgrade

ashika112 commented 1 week ago

@didemkkaslan Thank you for confirming that fix works as of latest 6.8.0. I will close this issue in favor. Please let us know if you find anything else 👍