Skip to content

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

@ibara1454

Description

@ibara1454

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 react@18.2.0 (any 18+ version should be fine)

Possible Reasons

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

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

  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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions