[React] ky로 로그인 유지 구현하기 (with. JWT 인증)

profile image pIutos 2024. 2. 16. 02:05

들어가며

ky는 fetch와 ESM을 기반으로 하는 HTTP Client를 제공하는 라이브러리입니다.

주로 사용되는 Axios의 interceptors와 비슷하게 ky는 응답을 intercept 하여 커스텀하게 조작이 가능한 AfterResponse, BeforeRequest와 같은 훅을 제공합니다.

따라서 해당 훅을 이용하여 JWT 인증을 통해 로그인 상태를 처리하는 앱에서, 로그인을 유지하도록 하는 방법을 소개하겠습니다.

API 요청 전 Header에 access token 담기

beforeRequest 훅에서는 HTTP request 이전에 수행 할 동작을 지정할 수 있습니다. 따라서 아래와 같이 accessToken을 Authorization Header에 담는 로직을 구현합니다.

import { BeforeRequestHook } from 'ky';
import storage from '../../storage';

const setAuthorizationHeader: BeforeRequestHook = request => {
  const accessToken = storage.getString('accessToken');

  if (!accessToken) return;
  request.headers.set('Authorization', `Bearer ${accessToken}`);
};

export default setAuthorizationHeader;

이후 기본 ApiClient의 beforeRequest hooks에 해당 함수를 지정합니다.

import setAuthorizationHeader from './beforeRequest';

export const apiClient = baseApiClient.extend({
  hooks: {
    beforeRequest: [setAuthorizationHeader],
  },
});

Retry로 access token 재발급 로직 구현하기

ky는 beforeRetry 훅을 통해 API fetch retry시 처리할 동작을 구현하는 것이 가능합니다.

코드 구성은 아래와 같습니다.

1. status code가 401이 아닌 경우 retry를 중지합니다.

2. access token을 2번 가져와도 실패한다면, 토큰 만료인 경우로 판단하여 로그아웃 처리를 합니다.(유저 정보와 token을 삭제하는 등)

3. 해당 1, 2번 경우가 아닐 때 refresh token을 이용하여 access token을 가져오고, 저장합니다.

import ky, {BeforeRetryHook} from 'ky';
import UserService from '../../services/user';
import {ErrorResponseType} from '../services/type';
import {DEFAULT_API_RETRY_LIMIT} from '../../configs/api';

const beforeRetry: BeforeRetryHook = async ({error, retryCount}) => {
  const customError = error as ErrorResponseType;
  if (customError.status !== 401) return ky.stop; // status code가 401이 아닌 경우 retry를 중지합니다.

  if (retryCount === DEFAULT_API_RETRY_LIMIT - 1) {
    await UserService.onLoginDurationExpired(); // access token을 2번 가져와도 실패한다면, 토큰 만료 로그아웃 시킵니다.
    return ky.stop;
  }
  await UserService.getAccessTokenByRefreshToken(); // refresh token을 이용하여 access token을 가져옵니다.
};

export default beforeRetry;

이후 해당 함수를 이전과 마찬가지로 beforeRetry에 지정합니다.

export const apiClient = baseApiClient.extend({
  timeout: DEFAULT_API_TIMEOUT, // 10 * 1000
  retry: {
    limit: DEFAULT_API_RETRY_LIMIT, // 4
    backoffLimit: DEFAULT_API_RETRY_BACKOFFLIMIT, // 3 * 1000
  },
  hooks: {
    beforeRequest: [setAuthorizationHeader],
    beforeRetry: [handleToken],
  },
});

마무리하며

위의 모든 설정을 마쳤다면, APIClient에 access token이 만료되었을 때 refresh token을 이용해 앱 로그인을 유지하도록 구현할 수 있습니다.