kinde-oss / kinde-auth-nextjs

Kinde NextJS SDK - authentication for server rendered apps
https://kinde.com/docs/developer-tools/nextjs-sdk/
MIT License
132 stars 17 forks source link

Bug: No support for expired sessions #172

Open Thinkscape opened 1 month ago

Thinkscape commented 1 month ago

Prerequisites

Describe the issue

The issue

Neither AuthProvider nor KindeBrowserClient respect the token expiry times and don't handle expired sessions and re-authentication. There is no mention of expiration handling anywhere in docs. Nothing in source code.

After the token expires, if the app is still open and tries to fetch data (as most RIA apps do) everything will start failing - i.e. with 405 as described in #171 or plain 403 401.

Expected

All other auth sdks I know provide implicit or explicit expiration handling - usually by checking the exp claim and making sure that before it expires (with some buffer), token is regenerated or redirection happens to auth page/route to prevent errors.

Library URL

https://github.com/kinde-oss/kinde-auth-nextjs

Library version

2.2.10

Operating system(s)

macOS

Operating system version(s)

23E224

Further environment details

No response

Reproducible test case URL

No response

Additional information

No response

Thinkscape commented 1 month ago

Here's a snippet from my custom TS implementation of the AuthProvider that supports token refresh:

// Auto-refresh token before it expires
  useEffect(() => {
    let timeoutId: Timer;

    const scheduleTokenRefresh = () => {
      if (state.accessToken) {
        try {
          const expClaim = state.getClaim("exp", "access_token");
          if (expClaim && typeof expClaim.value === "number") {
            const currentTime = Math.floor(Date.now() / 1000);
            const timeToExpiry = expClaim.value - currentTime;

            // Refresh the token 60 seconds before it actually expires
            if (timeToExpiry > 60) {
              timeoutId = setTimeout(
                // eslint-disable-next-line @typescript-eslint/no-misused-promises
                async () => {
                  await checkSession();
                },
                (timeToExpiry - 60) * 1000,
              );
            }
          }
        } catch (error) {
          console.error("Error fetching exp claim:", error);
        }
      }
    };

    void scheduleTokenRefresh();

    return () => {
      timeoutId && clearTimeout(timeoutId);
    };
  }, [state.accessToken, state.getClaim, checkSession, state]);

Note: checkSession() is a fixed and cleaned up TS version of https://github.com/kinde-oss/kinde-auth-nextjs/blob/main/src/frontend/AuthProvider.jsx#L61

Thinkscape commented 1 month ago

Here's a TS implementation of tokenFetcher() with support for redirecting the browser session to login page in case auth has expired while the page is still open:

const tokenFetcher = async (
  url: string,
  router: ReturnType<typeof useRouter>,
): Promise<KindeSetupResponse | undefined> => {
  let response;
  try {
    response = await fetch(url);
  } catch (e) {
    throw new Error("AuthProvider: Failed to fetch auth data", {
      cause: e as Error,
    });
  }

  if (response.ok) {
    return await response.json();
  } else {
    if (response.status === 401) {
      const loginUrl = response.headers.get("location") || LOGIN_PAGE;
      logger.debug(
        "AuthProvider: Auth token expired, redirecting to login page",
        { location: loginUrl },
      );
      router.push(loginUrl);
    } else {
      logger.error(
        "AuthProvider: Unknown error while trying to obtain auth data",
        {
          response: {
            status: response.status,
            statusText: response.statusText,
          },
        },
      );
    }
  }
};
tak0m0 commented 4 days ago

I having the same problem. I think that problem is very serious, is there any progress??😢