Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refresh token with ky: 2 calls instead of 1 #587

Open
ericledonge-betonprovincial opened this issue May 22, 2024 · 1 comment
Open

Refresh token with ky: 2 calls instead of 1 #587

ericledonge-betonprovincial opened this issue May 22, 2024 · 1 comment

Comments

@ericledonge-betonprovincial

I don't understand why, when I have a 401 on a http request, my app is calling 2 times the refresh token http endpoint.

Screenshot 2024-05-22 at 3 53 28 PM

My http.services.ts:

import ky, { HTTPError } from "ky";

import { getAccessToken, refreshAccessToken } from "./auth.services";
import { Config } from "../../config";

const createHttpInstance = () => {
  const API_URL = Config.apiUrl;

  const customKy = ky.extend({
    prefixUrl: API_URL,
    hooks: {
      beforeRequest: [
        async (request) => {
          const accessToken = await getAccessToken();
          if (accessToken) {
            request.headers.set("Authorization", `Bearer ${accessToken}`);
            request.headers.set("Content-Type", "application/json");
          }
        },
      ],
      beforeRetry: [
        async ({ request, error, retryCount }) => {
          if (
            error instanceof HTTPError &&
            error.response.status === 401 &&
            retryCount === 1
          ) {
            try {
              const newAccessToken = await refreshAccessToken();
              request.headers.set("Authorization", `Bearer ${newAccessToken}`);
            } catch (error) {
              throw new Error("Failed to refresh token");
            }
          }
        },
      ],
    },
    retry: {
      methods: ["get", "post"],
      limit: 5,
      statusCodes: [401],
    },
  });

  return customKy;
};

export const httpClient = createHttpInstance();

My auth.services.ts:

export const refreshAccessToken = async () => {
  try {
    const accessToken = await getAccessToken();
    const refreshToken = await getRefreshToken();

    if (!accessToken || !refreshToken) {
      await forcedLogout();
      return;
    }

    const response = await httpClient.post(URL_REFRESH_TOKEN, {
      json: { AccessToken: accessToken, RefreshToken: refreshToken },
    });
    const responseData = await response.json();
    const validData = signInResponseSuccessSchema.parse(responseData);

    await saveAccessToken(validData.resource.token);
    await saveRefreshToken(validData.resource.refreshToken);

    return validData.resource.token;
  } catch (error: any) {
    if (error.name === "HTTPError") {
      const errorJson = await error.response.json();
      const errorMessage = getErrorMessage(errorJson);
      logError(errorMessage);
    }

    await forcedLogout();

    throw new Error("Failed to refresh token");
  }
};
@sholladay
Copy link
Collaborator

sholladay commented Jun 24, 2024

I believe I’ve seen this behavior myself. There’s probably an off-by-one bug somewhere in Ky’s retry logic.

In your beforeRetry hook, does retryCount increment correctly? If you log it, what do you get?

There’s also #233, which is probably unrelated but worth investigating.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants