[JWT인증] Accesstoken, refreshtoken 관리하기

이은지·2024년 8월 7일
0

React

목록 보기
11/11

📍JWT인증에서 프론트엔드의 역할

  • 프론트엔드에서는 서버가 보내주는 토큰을 받고, 저장한 후 인증 요청이 필요할 때 서버에 토큰을 보낼 수 있도록 해야한다.

🔆토큰 저장

1. AccessToken

  • AccessToken은 인증에 직접적으로 사용되는 토큰이기 때문에 외부에 노출되지 않도록 코드 내의 변수에 저장
  • 코드 내의 변수로 저장

2. RefreshToken

  • RefreshToken은 인증에 직접적으로 사용하는 토큰이 아니지만, RefreshToken이 없어지면 로그인을 통해 다시 토큰을 받아야 하기 때문에 브라우저 어딘가에 저장이 되어야 함
  • 로컬 스토리지 or 쿠키
    • 둘 다 탈취의 위험이 존재
      • 로컬 스토리지는 XSS(Cross Site Scripting) 공격에 취약
        • localStorage 안에 세션 id, refreshToken 또는 accessToken을 저장해두면 XSS 취약점을 통해 그 안에 담긴 값을 불러오거나, 불러온 값을 이용해 API 콜을 위조할 수 있다.
      • 쿠키는 CSRF(Cross-Site Request Forgery)공격에 취약
        • 쿠키에 refreshToken만 저장하고 새로운 accessToken을 받아와 인증에 이용하는 구조에서는 CSRF 취약점 공격을 방어할 수 있다. refreshToken으로 accessToken을 받아도 accessToken을 스크립트에 삽입할 수 없다면 accessToken을 사용해 유저 정보를 가져올 수 없기 때문이다.
  • RefreshToken은 쿠키에 저장하여 사용
const onLogInSuccess = (accessToken: string, refreshToken: string) => {
  //accessToken 헤더에 설정
  axiosInstance.defaults.headers.common['Authorization'] =
    `Bearer ${accessToken}`;
  // refreshToken을 쿠키에 저장
  document.cookie = `refreshToken=${refreshToken}; path=/; `;
};

🔆토큰 재발급

  • AccessToken을 변수로 사용했으므로, 새로 고침 시 변수로 저장된 AccessToken이 사라질 것이기 때문에 새로고침 시 RefreshToken을 이용해 AccessToken을 받아와서 변수로 저장되게 처리
  const cookie = new Cookies();
  const { isLoggedIn, login } = useAuth();
  useEffect(() => {
    const refreshToken = cookie.get('refreshToken');
    if (refreshToken && refreshToken.length > 0) {
      onSilentRefresh().then(() => {
        login();
      });
    }
    // 의존성 배열을 비워 컴포넌트가 마운트될 때만 실행되도록 설정
  }, []);
export const onSilentRefresh = async () => {
  const cookies = new Cookies();
  const refreshToken = cookies.get('refreshToken');

  try {
    const res = await Post<refreshData[]>('/api/auth/token/refresh', {
      refreshToken: refreshToken,
    });
    if (res.status == 200) {
      const { accessToken } = res.data.data[0];
      onLogInSuccess(accessToken, refreshToken); //토큰 갱신
      console.log(res);
    }
  } catch (error) {
    console.log(error);
    console.log(error);
    if (axios.isAxiosError<CommonError>(error) && error.response) {
      const errorCode = error.response.data.errorCode;
      const message = error.response.data.message;
      console.log(`${errorCode}: ${message}`);
    }
    //로그인 페이지 리다이렉트
    alert('로그인이 필요합니다.');
    window.location.href = '/login';
  }
};

(참고)

https://velog.io/@badahertz52/프론트-엔드에서-JWT-AccessToken-RefreshToken-다루기#2-프론트에서-accesstoken-refreshtoken-다루기

https://velog.io/@yaytomato/프론트에서-안전하게-로그인-처리하기#-브라우저-저장소-종류와-보안-이슈

https://overcome-the-limits.tistory.com/611

profile
소통하는 개발자가 꿈입니다!

0개의 댓글