react-oidc-context icon indicating copy to clipboard operation
react-oidc-context copied to clipboard

Duplicate refresh access token call (automatic silent renew) when React Strict Mode is enabled

Open ibara1454 opened this issue 7 months ago • 3 comments

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

Image 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 [email protected] (any 18+ version should be fine)

Possible Reasons

In AuthProvider.ts, UserManagerImpl is instantiated in the useState's initializer function.

https://github.com/authts/react-oidc-context/blob/b6d9e22e4a034652c36a10cae96824a913eb719d/src/AuthProvider.tsx#L168-L173

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

  1. Inconsistent State and Race Conditions: When two UserManager instances 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.
  2. 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.

ibara1454 avatar Jun 04 '25 10:06 ibara1454

The simultaneous invocation of the refresh token endpoint doubles the number of requests sent to the authentication server.

StrictMode has no effect in production.

JesusTheHun avatar Jul 22 '25 15:07 JesusTheHun

The simultaneous invocation of the refresh token endpoint doubles the number of requests sent to the authentication server.

StrictMode has no effect in production.

This is true, but having just spent several hours trying to track down the reason for this behaviour when doing some local testing it is unhelpful at best! The second call often errors as mentioned, causing application error handling to kick in.

Strict mode is there for a reason and trying to code around it is messy. Happy to try and come up with a PR to resolve if that would be helpful!

cjpbright avatar Aug 05 '25 16:08 cjpbright

See also this unresolved issue from 2022: https://github.com/authts/react-oidc-context/issues/390

That one contains a reference to a change auth0-react had done around that time (https://github.com/auth0/auth0-react/pull/355) with the intention of solving a similar issue (albeit without the timer complication). The solution they implemented, however, is discouraged by React (using a ref to escape an effect).

If I understand your analysis correctly @ibara1454, the issue isn't really with this package, but with the implementation of UserManager in oidc-client-ts?

AndreasMaros avatar Nov 12 '25 15:11 AndreasMaros