5. 리액트에서 쿠키는 어떻게 사용할까
2025년 9월 14일
이게머니
기존 카카오로그인 기능에서, 임시로 로컬스토리지에 저장을 하는 것으로 설정해 두었습니다.
하지만 로그인 토큰을 로컬스토리지에 저장을 하는 것은 굉장히 보안적으로 위험한 상황이 될 수 있습니다. 저희는 사전에 쿠키에 저장하기로 회의를 진행했습니다.
쿠키를 적용하는 방법으로는 , react-cookie 라이브러리를 사용하였습니다.
그리고 기본적인 쿠키 유틸함수 파일을 만들었습니다.
1. 쿠키 기본 세팅
import { useCookies } from 'react-cookie'; /** * 쿠키 설정 옵션 타입 */ export interface CookieOptions { path?: string; maxAge?: number; secure?: boolean; sameSite?: 'strict' | 'lax' | 'none'; } /** * 기본 쿠키 옵션 */ const DEFAULT_COOKIE_OPTIONS: CookieOptions = { path: '/', maxAge: 7 * 24 * 60 * 60, // 7일 secure: true, sameSite: 'strict', }; /** * 쿠키 설정 훅 * @param cookieNames 관리할 쿠키 이름들 * @returns 쿠키 객체와 설정 함수 */ export const useCookieManager = (cookieNames: string[]) => { const [cookies, setCookie] = useCookies(cookieNames); /** * 쿠키 설정 * @param name 쿠키 이름 * @param value 쿠키 값 * @param options 쿠키 옵션 */ const setCookieValue = (name: string, value: string, options?: Partial<CookieOptions>) => { setCookie(name, value, { ...DEFAULT_COOKIE_OPTIONS, ...options, }); }; /** * 쿠키 삭제 * @param name 쿠키 이름 */ const removeCookie = (name: string) => { setCookie(name, '', { path: '/', maxAge: 0, }); }; return { cookies, setCookie: setCookieValue, removeCookie, }; }; /** * 토큰 관련 쿠키 관리 훅 */ export const useTokenCookies = () => { const { cookies, setCookie, removeCookie } = useCookieManager(['access_token', 'refresh_token']); /** * 액세스 토큰 설정 * @param token 토큰 값 * @param days 만료일 (기본값: 7일) */ const setAccessToken = (token: string, days: number = 7) => { setCookie('access_token', token, { maxAge: days * 24 * 60 * 60, }); }; /** * 리프레시 토큰 설정 * @param token 토큰 값 * @param days 만료일 (기본값: 30일) */ const setRefreshToken = (token: string, days: number = 30) => { setCookie('refresh_token', token, { maxAge: days * 24 * 60 * 60, }); }; /** * 액세스 토큰 삭제 */ const removeAccessToken = () => { removeCookie('access_token'); }; /** * 리프레시 토큰 삭제 */ const removeRefreshToken = () => { removeCookie('refresh_token'); }; /** * 모든 토큰 삭제 */ const clearTokens = () => { removeAccessToken(); removeRefreshToken(); }; return { accessToken: cookies.access_token, refreshToken: cookies.refresh_token, setAccessToken, setRefreshToken, removeAccessToken, removeRefreshToken, clearTokens, }; };
지난 멘토님의 리뷰를 토대로 주석을 JSDoc 스타일로 작성하여 다른 팀원이 쉽게 이해할 수 있게 진행하였습니다.
기본 쿠키 옵션으로는, 경로, 만료시간, https만 전송, CSRF 공격 방지 설정을 해 두었습니다.
1. 자동 Bearer 토큰 헤더 추가
api.interceptors.request.use( (config) => { const cookies = document.cookie.split(';'); const accessTokenCookie = cookies.find((cookie) => cookie.trim().startsWith('access_token=')); if (accessTokenCookie) { const accessToken = accessTokenCookie.split('=')[1]; if (accessToken) { config.headers.Authorization = `Bearer ${accessToken}`; } } return config; }, (error) => { return Promise.reject(error); }, );
accessTokenCookie가 있으면, 자동으로 헤더에 이를 포함시킵니다.
2. 토큰 자동 갱신
/** * 401 에러 처리 함수 */ export const handle401Error = async ( originalRequest: InternalAxiosRequestConfig, ): Promise<unknown> => { // 토큰 갱신 시도 const newAccessToken = await refreshAccessToken(); if (newAccessToken) { // 새로운 토큰으로 원래 요청 재시도 originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; return api(originalRequest); } else { // 토큰 갱신 실패 시 로그아웃 handleLogout(); return Promise.reject(new Error('토큰 갱신 실패')); } };
토큰을 자동갱신하고, 갱신 실패시 로그아웃하는 로직을 구현하였습니다
3. 인증 상태 관리
import { useState, useEffect } from 'react'; import { useTokenCookies } from '@/utils/cookie'; import { useNavigate } from 'react-router-dom'; export const useAuth = () => { const { accessToken, refreshToken, clearTokens } = useTokenCookies(); const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(true); const navigate = useNavigate(); useEffect(() => { setIsAuthenticated(!!accessToken); setIsLoading(false); }, [accessToken]); const logout = () => { clearTokens(); localStorage.removeItem('userId'); setIsAuthenticated(false); navigate('/login'); }; const checkAuth = () => { return !!accessToken; }; return { isAuthenticated, isLoading, accessToken, refreshToken, logout, checkAuth, }; };
결과
추후 api 통신에는, 토큰이 자동으로 헤더에 추가가 될 것입니다.