Description
Before opening, please confirm:
- I have searched for duplicate or closed issues and discussions.
- I have read the guide for submitting bug reports.
- I have done my best to include a minimal, self-contained set of instructions for consistently reproducing the issue.
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.
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
- Login
- Copy refresh token from local storage (this is needed for AWS CLI command)
- Run AWS CLI commands to revoke the refresh token.
- Clear the network tab and filter on "cognito"
- Wait approximately xxx minutes to allow the current token to expire.
- 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