refine icon indicating copy to clipboard operation
refine copied to clipboard

[BUG] Can't login success after session expired(loop in authProvider onError)

Open huntkalio opened this issue 3 months ago • 14 comments

Describe the bug

I upgrade refine from 4.x to 5.x. In new 5.x version, I found this bug. When my web session is expired and the server return 401, then refine redirect to login page.But I can't log in unless I refresh the login page。Because the refine loop in reminding me 401 error when I log in again,even the login interface returns success。

Steps To Reproduce

1.login in 2.go to A menu 3. delete cookie in brower 4. go to B menu 5. now will redirect to login 6. login again will failed because always call authProvider.onError

Expected behavior

login success after session expired(

Packages

"@refinedev/antd": "^6.0.1",
"@refinedev/cli": "^2.16.48",
"@refinedev/core": "^5.0.1",
"@refinedev/devtools": "^2.0.1",
"@refinedev/kbar": "^2.0.0",
"@refinedev/react-router": "^2.0.0",
"@refinedev/simple-rest": "^6.0.0",

Additional Context

No response

huntkalio avatar Sep 16 '25 06:09 huntkalio

<html>
<body>
<!--StartFragment-->
onError | @ | authProvider.ts:115
-- | -- | --
  | fn | @ | chunk-UJSJH2WS.js?v=735b857f:5426
  | run | @ | chunk-UJSJH2WS.js?v=735b857f:4220
  | start | @ | chunk-UJSJH2WS.js?v=735b857f:4263
  | execute | @ | chunk-UJSJH2WS.js?v=735b857f:5462
  | await in execute |   |  
  | mutate | @ | chunk-UJSJH2WS.js?v=735b857f:5782
  | (匿名) | @ | chunk-UJSJH2WS.js?v=735b857f:6438
  | U | @ | chunk-UJSJH2WS.js?v=735b857f:8792
  | commitHookEffectListMount | @ | chunk-WUVWK3JO.js?v=735b857f:16936
  | commitPassiveMountOnFiber | @ | chunk-WUVWK3JO.js?v=735b857f:18184
  | commitPassiveMountEffects_complete | @ | chunk-WUVWK3JO.js?v=735b857f:18157
  | commitPassiveMountEffects_begin | @ | chunk-WUVWK3JO.js?v=735b857f:18147
  | commitPassiveMountEffects | @ | chunk-WUVWK3JO.js?v=735b857f:18137
  | flushPassiveEffectsImpl | @ | chunk-WUVWK3JO.js?v=735b857f:19518
  | flushPassiveEffects | @ | chunk-WUVWK3JO.js?v=735b857f:19475
  | commitRootImpl | @ | chunk-WUVWK3JO.js?v=735b857f:19444
  | commitRoot | @ | chunk-WUVWK3JO.js?v=735b857f:19305
  | performSyncWorkOnRoot | @ | chunk-WUVWK3JO.js?v=735b857f:18923
  | flushSyncCallbacks | @ | chunk-WUVWK3JO.js?v=735b857f:9135
  | (匿名) | @ | chunk-WUVWK3JO.js?v=735b857f:18655
  | setTimeout |   |  
  | systemSetTimeoutZero | @ | chunk-UJSJH2WS.js?v=735b857f:3677
  | flush | @ | chunk-UJSJH2WS.js?v=735b857f:4038
  | batch | @ | chunk-UJSJH2WS.js?v=735b857f:4056
  | dispatch_fn | @ | chunk-UJSJH2WS.js?v=735b857f:4685
  | setData | @ | chunk-UJSJH2WS.js?v=735b857f:4356
  | fetch | @ | chunk-UJSJH2WS.js?v=735b857f:4578
  | await in fetch |   |  
  | executeFetch_fn | @ | chunk-UJSJH2WS.js?v=735b857f:5072
  | onSubscribe | @ | chunk-UJSJH2WS.js?v=735b857f:4768
  | subscribe | @ | chunk-UJSJH2WS.js?v=735b857f:3598
  | (匿名) | @ | chunk-UJSJH2WS.js?v=735b857f:6356
  | subscribeToStore | @ | chunk-WUVWK3JO.js?v=735b857f:12004
  | commitHookEffectListMount | @ | chunk-WUVWK3JO.js?v=735b857f:16936
  | commitPassiveMountOnFiber | @ | chunk-WUVWK3JO.js?v=735b857f:18184
  | commitPassiveMountEffects_complete | @ | chunk-WUVWK3JO.js?v=735b857f:18157
  | commitPassiveMountEffects_begin | @ | chunk-WUVWK3JO.js?v=735b857f:18147
  | commitPassiveMountEffects | @ | chunk-WUVWK3JO.js?v=735b857f:18137
  | flushPassiveEffectsImpl | @ | chunk-WUVWK3JO.js?v=735b857f:19518
  | flushPassiveEffects | @ | chunk-WUVWK3JO.js?v=735b857f:19475
  | performSyncWorkOnRoot | @ | chunk-WUVWK3JO.js?v=735b857f:18896
  | flushSyncCallbacks | @ | chunk-WUVWK3JO.js?v=735b857f:9135
  | commitRootImpl | @ | chunk-WUVWK3JO.js?v=735b857f:19460
  | commitRoot | @ | chunk-WUVWK3JO.js?v=735b857f:19305
  | finishConcurrentRender | @ | chunk-WUVWK3JO.js?v=735b857f:18833
  | performConcurrentWorkOnRoot | @ | chunk-WUVWK3JO.js?v=735b857f:18746
  | workLoop | @ | chunk-WUVWK3JO.js?v=735b857f:197
  | flushWork | @ | chunk-WUVWK3JO.js?v=735b857f:176
  | performWorkUntilDeadline | @ | chunk-WUVWK3JO.js?v=735b857f:384

<!--EndFragment-->
</body>
</html>

huntkalio avatar Sep 16 '25 06:09 huntkalio

Image

I print the server response.headers.date, you can see it print 8 times with same date value.( The number of lines of code may be incorrect because I deleted some irrelevant code)

loop in if (error.response?.status === 401) even if server return http 200 OK. onError triggered many times. And refine always notify ‘Error (status code: 401)’ even if server return 200.

import type { AuthProvider } from "@refinedev/core";
import { MyKey, UserInfo } from "../interfaces";
import { v5 as uuidv5 } from "uuid";
import Cookies from "js-cookie";

export const authProvider = (apiUrl: string): AuthProvider  => {

  return {
    login: async ({ username, email, password, providerName}) => {
    
      const loginUrl = `${apiUrl}/login/signin`;

      const response = await fetch(
        loginUrl,
        {
          method: "POST",
          body: JSON.stringify({ email, password }),
          headers: {
            "Content-Type": "application/json",
          },
        },
      );

      const http_response = await response.json();

      if (http_response.code === 10000 && http_response.data.token) {
        localStorage.setItem(MyKey.IAM_TOKEN, http_response.data.token);
        localStorage.setItem(MyKey.IAM_USERINFO, JSON.stringify(http_response.data.userInfo));
        return {
          success: true,
          redirectTo: "/",
        };
      }

      return {
        success: false,
        error: {
          name: "LoginError",
          message: "Invalid username or password",
        },
      };
    },
    logout: async () => {
      localStorage.removeItem(MyKey.IAM_TOKEN);
      localStorage.removeItem(MyKey.IAM_USERINFO);
      Cookies.remove("sessionId", { path: "/" });
      return {
        success: true,
        redirectTo: "/login",
      };
    },
    check: async () => {
      const token = localStorage.getItem(MyKey.IAM_TOKEN);
      if (token) {
        return {
          authenticated: true,
        };
      }

      return {
        authenticated: false,
        redirectTo: "/login",
      };
    },
    getPermissions: async () => null,
    getIdentity: async () => {
      const userInfoString = localStorage.getItem(MyKey.IAM_USERINFO);

      if (userInfoString) {
        let userInfo: UserInfo = JSON.parse(userInfoString);
        return {
          id: userInfo.username,
          name: userInfo.displayName,
          emailAddress: userInfo.emailAddress,
          groups: userInfo.groups,

        };
      }
      return null;
    },
    onError: async (error) => {
      //console.error("authProvider: " + error);
      console.error("authProvider: " + error.response.headers.date);

      if (error.response?.status === 401) {
        return {
          logout: true,
        };
      }
  
      return { error };
    },
  };
};

huntkalio avatar Sep 16 '25 06:09 huntkalio

Hello @huntkalio can you update the latest versions and try again? If not, we appreciate if you can create a repository with reproduced issue.

BatuhanW avatar Sep 22 '25 06:09 BatuhanW

Hello @huntkalio can you update the latest versions and try again? If not, we appreciate if you can create a repository with reproduced issue.

@BatuhanW after update, the problem is not resolved.The reproduced code can see in https://github.com/huntkalio/refine-test

1.fisrt login, then goto blog-posts page 2.f12 and delete brower cookie name 'sessionIdxx' 3.go to categories pages by click on categories menu 4.then will goto login page 5.then click login button,will not loggin

https://github.com/user-attachments/assets/4d9e241b-3f05-4b09-a0dd-f7280cc80b0c

huntkalio avatar Sep 22 '25 07:09 huntkalio

Hey @huntkalio, I couldn't replicate the issue with the repo you provided.

https://github.com/user-attachments/assets/16a95cae-322c-4f8a-b695-2916941d7b77

I tried the following after that

  • npm ls @refinedev/core @refinedev/antd @refinedev/react-router @refinedev/simple-rest
    • the installed versions match exactly what is in the pkg-json
  • `npm ci
    • no difference in app behaviour from npm i

The issue may be how your browser handles the cookie. I'm on Chrome v140.

arndom avatar Oct 04 '25 14:10 arndom

@arndom This is a sporadic issue, but it's quite common if you click quickly enough. I've reproduced this issue on both Chrome 141.0.7390.66 and Firefox.When the login fails, the browser does not request https://mock.httpstatus.io/401?_end=10&_start=0 again, but the 401 error keeps popping up.

https://github.com/user-attachments/assets/35bdc4f7-9c21-4187-98dd-87ab40d33dce

https://github.com/user-attachments/assets/7439f7a5-da3f-46a8-b73f-bdd8cb1debe7

huntkalio avatar Oct 09 '25 02:10 huntkalio

are you lot using a monorepo?

smashah avatar Oct 11 '25 02:10 smashah

@smashah no

@huntkalio I was finally able to reproduce this on Chrome v141 and on another system with v140(no idea why it didn't throw prev.)

I'll see if I can find the cause of the issue.

arndom avatar Oct 11 '25 13:10 arndom

@huntkalio The bug seems to be a result of the move to TanStack Query v5...useList now caches differently as it is on top of useQuery.

Previously in v4, when there is a cached error state, once a component that accesses that query key is remounted, there is a refetch. In v5, there is no refetch; the cached error state gets sent back. So now we have to either explicitly refetch or invalidate the query on login.

It took a while to figure out, but using React Query devtools helped me see what was being sent.

@BatuhanW is this something that should be handled by refine in auth providers, or does it become the user's job?

arndom avatar Oct 18 '25 22:10 arndom

I'm having the same issue even though the server returns 200 on the query.

dspatoulas avatar Nov 02 '25 18:11 dspatoulas

@arndom Thanks for finding the bug. I think this should be fixed within the Refine auth provider system. Would you like to work on this, or should I take a look?

alicanerdurmaz avatar Nov 03 '25 10:11 alicanerdurmaz

@alicanerdurmaz could you point me in the right direction?

I intended to invalidate all queries on logout, but I'm stuck on accessing the current queryClient because it's defined within the <Refine> component, and it also depends on the component's props. Here

arndom avatar Nov 03 '25 14:11 arndom

Hi @arndom, I think you can use useQueryClient hook to get the client.

BatuhanW avatar Nov 17 '25 08:11 BatuhanW

yh, that should work. I was wrongly thinking of how to call the hook in the authProvider object, overlooking calling in the provider itself🤦‍♂️.

arndom avatar Nov 17 '25 09:11 arndom