다님 서비스 프로젝트에서 로그인과 토큰 저장은 아래와 같이 사용함
- 로그인 시 서버로부터 액세스 토큰과 리프레쉬 토큰을 응답 헤더로 받음
- 클라이언트가 응답 헤더에서 토큰을 꺼내어 쿠키에 저장
- 인증 인가가 필요한 api 요청시 쿠키에서 꺼낸 액세스 토큰 사용
- 인증 인가 필요한 페이지 라우팅에서 액세스 토큰 검사하고(단순히 쿠키 존재 여부 검사) 존재하지 않는다면(=만료) 리프레쉬 토큰 가지고 액세스 재발급
코드 작성 이후 보안과 관련해서 문제점이 있다는 것을 알게됐음
일단 보안이 뚫리는 공격들에 대해서 간단하게 정리하자면
일단 기존의 코드에서 쿠키에 토큰을 저장하는 부분에서 악성 스크립트를 이용해서 토큰을 탈취당할 수 있음을 인지하게 됨
따라서 쿠키에 저장하지 않고 사용할 수 있게 코드를 수정하려고 함
이러한 수정 계획을 짰음
울팀 서버 개발자 영구님과 함께 시작~ 🙂
// 주요 코드 생략
// as-is
// 응답 헤더의 액세스 토큰과 리프레쉬 토큰을 쿠키에 저장함
const accessToken = response.headers.access_key;
const refreshToken = response.headers.refresh_key;
if (accessToken && refreshToken) {
setCookie("accessToken", accessToken, 1);
setCookie("refreshToken", refreshToken, 14);
}
// to-be
// 응답의 payload로 액세스 토큰을 받아 인스턴스 헤더에 설정
const {accessToken, ...} = response.data.data;
axiosInstance.defaults.headers.common.ACCESS_KEY = `${accessToken}`;
먼저 액세스 토큰에 대한 코드를 변경했음
기존 코드에서는 응답 헤더에 있던 액세스 토큰과 리프레쉬 토큰을 쿠키에 저장했었음
하지만 header가 아닌 payload로 액세스 토큰을 전달받아서 바로 instance의 header로 설정함. 리프레쉬 토큰은 httpOnly 설정을 통해 서버에서만 관리하기 때문에 따로 코드를 작성하지 않음
이렇게 변경하고 로그인을 시도해서 기타 토큰이 필요한 요청들을 테스트해봤는데 정상적으로 기능했음. 하지만 새로고침 시 401 에러 발생
원인은 웹 브라우저에서 페이지를 새로고침하면 JavaScript의 메모리가 초기화 되기 때문
따라서 JavaScript로 실행되는 모든 코드와 변수, 객체 등의 상태가 초기화 => axios 인스턴스와 그 헤더 설정도 초기화 😇
새로고침 할 때 토큰을 새로 재발급 받으면 되지 않을까 해서
가장 최상위 컴포넌트인 App.tsx가 마운트 될때 토큰 재발급 함수를 사용하기로 결정함
// APP.tsx
// 새로고침 했을때 액세스 토큰 재발급
useEffect(() => {
refreshAccessToken();
}, []);
// signUp.ts
export const refreshAccessToken = async () => {
try {
// 토큰 갱신 요청은 refreshInstance를 사용하여 보내기
const response = await refreshInstance.get("/api Endpoint");
const { accessToken } = response.data.data;
axiosInstance.defaults.headers.common.ACCESS_KEY = `${accessToken}`;
return accessToken;
} catch (err: any) {
console.log("여기서 에러", err);
const errMessage = err.response.data.detail || err.message;
return errMessage;
}
};