-
Notifications
You must be signed in to change notification settings - Fork 93
Description
Describe the bug
The refresh token endpoint is called twice simultaneously when React Strict Mode is enabled.
This occurs when:
- It's the first rendering of
<AuthProvider> - Or, after waiting for a few minutes, the access token refresh is processed
depending on whether the access token is still valid.
How to Reproduce
(Not always reproducible)
Follow the instructions in the README to create the simplest application is sufficient (but with the <StrictMode>).
// src/index.jsx
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { AuthProvider } from "react-oidc-context";
import App from "./App";
const oidcConfig = {
authority: "<your authority>",
client_id: "<your client id>",
redirect_uri: "<your redirect uri>",
// ...
};
ReactDOM.render(
<StrictMode>
<AuthProvider {...oidcConfig}>
<App />
</AuthProvider>
</StrictMode>,
document.getElementById("app")
);// src/App.jsx
import React from "react";
import { useAuth } from "react-oidc-context";
function App() {
const auth = useAuth();
switch (auth.activeNavigator) {
case "signinSilent":
return <div>Signing you in...</div>;
case "signoutRedirect":
return <div>Signing you out...</div>;
}
if (auth.isLoading) {
return <div>Loading...</div>;
}
if (auth.error) {
return <div>Oops... {auth.error.kind} caused {auth.error.message}</div>;
}
if (auth.isAuthenticated) {
return (
<div>
Hello {auth.user?.profile.sub}{" "}
<button onClick={() => void auth.removeUser()}>Log out</button>
</div>
);
}
return <button onClick={() => void auth.signinRedirect()}>Log in</button>;
}
export default App;Then, sign in with your account and wait for a few minutes for the access token refresh to be processed.
Expected behavior
The refresh token endpoint SHOULD NOT be called twice simultaneously when React Strict Mode is enabled.
Screenshots/Video

In the screenshot, the 2 token requests are sent simultaneously. You can refer to the request call stack to see how this happened.
Version
Happens in both:
- 3.3.0
- 2.3.1
when using react@18.2.0 (any 18+ version should be fine)
Possible Reasons
In AuthProvider.ts, UserManagerImpl is instantiated in the useState's initializer function.
react-oidc-context/src/AuthProvider.tsx
Lines 168 to 173 in b6d9e22
| const [userManager] = React.useState(() => { | |
| return userManagerProp ?? | |
| (UserManagerImpl | |
| ? new UserManagerImpl(userManagerSettings as UserManagerSettings) | |
| : ({ settings: userManagerSettings } as UserManager)); | |
| }); |
When a new UserManager is constructed, it appears to schedule a timer to refresh the access token (a side effect).
https://github.com/authts/oidc-client-ts/blob/521df1316ba353c2b22e84214326002149f38eed/src/UserManager.ts#L107-L109
According to https://react.dev/reference/react/useState#usestate, the initializer function must be a pure function, and React will call the initializer function twice in order to help us find impurities when the strict mode is enabled. This results in the initializer function being called twice, creating 2 UserManager instances and scheduling two separate timers to refresh the access token simultaneously.
Why It Is a Problem
- Inconsistent State and Race Conditions: When two
UserManagerinstances are created, they may independently attempt to refresh the token. This can lead to race conditions where the client receives conflicting responses (e.g., one token is valid, the other is expired), causing the application to lose track of the user's authentication state. Additionally, if the server invalidates the token after the first refresh request, the second request may fail, leaving the client in an inconsistent state. This could force the user to re-authenticate or cause unexpected errors in the application. - Unintended Server Load: The simultaneous invocation of the refresh token endpoint doubles the number of requests sent to the authentication server. This can overwhelm the server, especially under high traffic, leading to potential rate-limiting, increased latency, or even service degradation.