Skip to content

Requesting IdToken causes TooManyRequestsException.  #13973

Closed as not planned
Closed as not planned
@al-mcnichol

Description

@al-mcnichol

Before opening, please confirm:

JavaScript Framework

Web Components

Amplify APIs

Authentication

Amplify Version

Older than v5

Amplify Categories

auth

Backend

None

Environment information

# Put output below this line
System:
    OS: Linux 5.15 Ubuntu 22.04.2 LTS 22.04.2 LTS (Jammy Jellyfish)
    CPU: (16) x64 11th Gen Intel(R) Core(TM) i9-11950H @ 2.60GHz
    Memory: 11.75 GB / 15.23 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 22.8.0 - ~/.nvm/versions/node/v22.8.0/bin/node
    npm: 10.8.2 - ~/.nvm/versions/node/v22.8.0/bin/npm
  npmPackages:
    @aws-amplify/auth: ^4.6.17 => 4.6.17
    @aws-amplify/core: ^4.7.15 => 4.7.15
    @optimizely/optimizely-sdk: ^5.3.4 => 5.3.4
    @stencil/core: ^4.22.0 => 4.22.0
    @stencil/core/cli:  4.22.0
    @stencil/core/compiler:  4.22.0
    @stencil/core/dev-server:  4.22.0
    @stencil/core/dev-server/client:  4.22.0
    @stencil/core/internal:  4.22.0
    @stencil/core/internal/app-data:  4.22.0
    @stencil/core/internal/client:  4.22.0
    @stencil/core/internal/hydrate:  4.22.0
    @stencil/core/internal/testing:  4.22.0
    @stencil/core/mock-doc:  4.22.0
    @stencil/core/screenshot:  4.22.0
    @stencil/core/sys/node:  4.22.0
    @stencil/core/testing:  4.22.0
    @stencil/sass: ^3.0.12 => 3.0.12
    @stencil/store: ^2.0.16 => 2.0.16
    @types/jest: ^29.5.13 => 29.5.13
    @types/node: ^22.7.4 => 22.7.4
    autoprefixer: ^10.4.20 => 10.4.20
    http-proxy: ^1.18.1 => 1.18.1
    http-server: ^14.1.1 => 14.1.1
    husky: ^9.1.6 => 9.1.6
    jest: ^29.7.0 => 29.7.0
    jest-cli: ^29.7.0 => 29.7.0
    jwt-decode: ^4.0.0 => 4.0.0
    mitt: ^3.0.0 => 3.0.1
    npm-check: ^6.0.1 => 6.0.1
    puppeteer: ^23.5.0 => 23.5.0
    rollup-plugin-node-polyfills: ^0.2.1 => 0.2.1
    stencil-tailwind-plugin: ^1.7.0 => 1.8.0
    tailwindcss: ^3.4.13 => 3.4.13
    typescript: ^5.6.2 => 5.6.2
    uuid: ^10.0.0 => 10.0.0 (3.4.0, 9.0.1)
  npmGlobalPackages:
    @aws-amplify/cli: 12.12.6
    corepack: 0.29.3
    npm: 10.8.2

Describe the bug

We upgraded v4 to v6 and within an hour started getting reports of users not being able to login. It turns out we had an overwhelming number of requests, surpassing our rate limit (120 request/sec), to Cognito, which in turn blocked the requests causing TooManyRequestsException.

We rolled back to v4 and through testing recognized the issue was there all along and somehow was exacerbated by the upgrade?

We can reproduce the issue in v4. It occurs when the refresh token expires and the library continually attempts to refresh the IdToken token from the server and we are therefore hitting the Cognito server hundreds of times, which causes us to be hit the rate limit.

For more context on reproducing the error – we login to the App and used AWS CLI (aws cognito-idp revoke-token) to revoke the refresh token. After some time any idToken request caused a network request to Cognito. The interesting part is that we’re listening for tokenRefresh_failure event from the HUB and calling Auth.signOut() , which triggers other code to remove any related cookies but the network requests continues.

NOTE: The repeated requests for the idToken is because it's being saved to a cookie to support an old app that authenticates on the server and not through client technologies. The app is using our new Authentication web component, which is client-side.

image

Expected behavior

Once the user is logged out due to token refresh expiration or refresh error any request to refresh to ID or Access token should not cause a network request to Cognito

Reproduction steps

  1. Login
  2. Copy refresh token from local storage (this is needed for AWS CLI command)
  3. Run AWS CLI commands to revoke the refresh token.
  4. Clear the network tab and filter on "cognito"
  5. Wait approximately xxx minutes to allow the current token to expire.
  6. Repeated calls to refresh the ID/Access tokens causes a network request

Code Snippet

import { Amplify, Hub } from '@aws-amplify/core';
import { Auth as AmplifyAuth, CognitoHostedUIIdentityProvider } from '@aws-amplify/auth';
import { Build } from '@stencil/core';
import config from './config';
import {
  FederatedAuthProviders,
  removeLocalUser,
  setCurrentAuthProvider,
  usernameToFederatedProvider,
  setAuthConumserIDCookies,
} from './core';

import { getConsumerId } from './index';
import { getFederatedAuthBroadcastChannel } from '../utils/federatedAuthBroadcastChannel';
import { emit } from '../utils/events';

import {
  loadFraudCollectorScript,
} from '../utils/fraudCollector';
import newrelicUtil from '../utils/newrelicUtil';
import BrowserStorage from '../utils/BrowserStorage';
import { noop } from '../utils/noop';
import { analytics } from '../utils/analytics';

loadFraudCollectorScript().catch(noop);

/**
 * If the opener has been disconnected from the new tab, the user will see a blank page if they remain
 * on /account-api/federated-auth-page. This will navigate them back to the same URL as the opener.
 */
const callingPageURLStorage = new BrowserStorage('auth-calling-page-url', { persist: true });
const avoidBlankPage = (federatedProvider?: FederatedAuthProviders) => {
  const currentURL = new URL(window.location.href);
  const nextURL = callingPageURLStorage.getItem() as string;
  if (!globalThis.opener && nextURL && currentURL.pathname === '/account-api/federated-auth-page') {
    callingPageURLStorage.removeItem();
    newrelicUtil.FEDERATED_AUTH(`Forced navigation to avoid blank page - ${federatedProvider}`);
    globalThis.location.href = nextURL;
  }
};

// WARNING: Amplify actually passes null instead of undefined for the payload.data in some cases, so we can't
// destructure payload.data in the function signature.
Hub.listen('auth', async ({ payload: { event, data } }) => {
  try {
    const federatedAuthBroadcastChannel = await getFederatedAuthBroadcastChannel();
    if (event === 'tokenRefresh_failure') {
      AmplifyAuth.signOut();
      newrelicUtil.AMPLIFY_AUTH_HANDLER('tokenRefresh_failure');
    } else if (['oAuthSignOut', 'signOut'].includes(event)) {
      analytics.SIGN_OUT_SUCCESS();
      removeLocalUser();
    } else if (event === 'autoSignIn') {
      await setCurrentAuthProvider('cognito');
      globalThis.dispatchEvent(new Event('auth.signin'));
      emit('auth.signin');
    } else if (['customOAuthState', 'signIn'].includes(event)) {
      // Process fraud collector data once sign-in event occurs
      const federatedProvider: FederatedAuthProviders = usernameToFederatedProvider(data?.username);
      const consumerId = await getConsumerId();
      setAuthConumserIDCookies(consumerId);
      if (federatedProvider) {
        const { waitForIdTokenWithConsumerId } = await import('./waitForIdTokenWithConsumerId');
        await waitForIdTokenWithConsumerId();
        federatedAuthBroadcastChannel.postMessage({ signIn: true, provider: federatedProvider });
        avoidBlankPage(federatedProvider);
      }
    } else if (['signIn_failure'].includes(event)) {
      if (data?.message) {
        if (data.message.startsWith('PreSignUp+failed+with+error+An+account+already+exists+with+a+different+spelling')) {
          federatedAuthBroadcastChannel.postMessage({ signIn: false, err: 'sameBaseEmail' });
          avoidBlankPage();
          return;
        }
        if (/^PreSignUp.fail/.test(data.message)) {
          federatedAuthBroadcastChannel.postMessage({ signIn: false, err: 'federatedEmailNotProvided' });
          avoidBlankPage();
          return;
        }
        if (data.message.startsWith('User+is+not+enabled')) {
          federatedAuthBroadcastChannel.postMessage({ signIn: false, err: 'accountDisabled' });
          avoidBlankPage();
          return;
        }
      }
      federatedAuthBroadcastChannel.postMessage({ signIn: false, err: 'genericErrorTryAgain' });
      avoidBlankPage();
    }
  } catch (err) {
    if (globalThis.location.pathname === '/account-api/federated-auth-page') {
      newrelicUtil.FEDERATED_AUTH(err);
    } else {
      newrelicUtil.AMPLIFY_AUTH_HANDLER(err);
    }
  }
});

Amplify.configure(config);

export const useCustomAuthFlow = () => {
  Amplify.configure({ authenticationFlowType: 'CUSTOM_AUTH' });
};
export const useUserSRPAuthFlow = () => {
  Amplify.configure({ authenticationFlowType: 'USER_SRP_AUTH' });
};

// Reexporting Auth this way instead of `export {Auth}` to prevent IDEs suggesting that importing Auth from this module
// is unnecessary.
export const Auth = AmplifyAuth;

// underlying apps are calling this utility functions.
export const getCognitoIdToken = async (): Promise<string> =>
  (await Auth.currentSession()).getIdToken().getJwtToken();
  
  
  

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

Metadata

Metadata

Assignees

Labels

AuthRelated to Auth components/categoryquestionGeneral question

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions