[React] Refresh Token 을 이용한 토큰 자동 갱신

사요·2022년 11월 6일
8

Refresh Token

보통 로그인 시스템을 구현할때에는 보안을 위하여 accessToken 의 만료시간을 짧게 하고, refreshToken 도 함께 발급하여 accessToken 이 탈취되더라도 금방 만료되게 하여(시간이 짧으면 털어갈 수 있는 정보가 한정적임) 보안성을 높인다.

즉, refreshToken 은 API 요청에 필요한 accessToken 의 기간이 만료 되었을 때 accessToken을 새롭게 발급받기 위하여 필요하다.

오늘은 이 refreshToken 으로 accessToken 을 자동으로 갱신하는 코드를 구현해 볼 예정이다.

Refresh Token 의 저장 위치

여태까지는 Refresh랑 Access가 같은 저장소(localStorage)에 보관이 되어있었다. 그러나 보안상의 이유로 Refresh Token을 도입하는데, Refresh Token이 AccessToken과 같은 위치에 저장되어 있다면 의미가 없지 않을까 하는 의문이 있었다. 아직 해킹쪽 지식이 없는 것도 맞지만, AccessToken이랑 Refresh Token 이 같은 위치에 저장이 되어있는데 해커가 AccessToken 만 채간다는 것이 이상하게 느껴졌다(은행을 털었는데 1억이 있는데 100만원만 가져가는 느낌). 따라서 localStorage보다 Cookie에 저장하는게 비교적 안전하다는 의견이 많으니, Cookie에 저장된 Refresh는 탈취가 어렵게 하여 보안성을 높이는 방식으로 코드를 구현하기로 하였다.

근데 그럴거면 둘 다 Cookie에 저장하지, 왜 Refresh 토큰만 Cookie에 저장하는 방식을 이용하는지도 아직도 의문이 남았다ㅎㅎ.

서버에서 온 Header의 Cookie 저장하기

https://developer.mozilla.org/ko/docs/Web/HTTP/Cookies

서버에서 클라이언트로 Set-Cookie HTTP 응답 헤더로 Refresh Token 을 전달해주시기로 했다.

Set-Cookie HTTP 응답 헤더는 서버에서 사용자 브라우저에 쿠키를 전송하기 위해 사용된다.

문제는.. 분명 Postman에서는 아래처럼 정상적으로 응답이 확인되는데, 브라우저에서는 response.header 콘솔로는 확인되지 않더라는 것이었다.

Network 탭에서 확인하니까 제대로 전달 받아진 것이 확인되었다.

그러나 Response Header 에 있는 쿠키가 브라우저에 제대로 저장된 것 이 맞는지 아닌지 확인할 겨를이 없어서 답답했다. 프론트에서 withCredentials 설정(쿠키 사용시 필수)이 true로 되어있지 않으면 제대로 보이지 않을 수도 있다는데 이 설정을 추가해도 마찬가지였다. 백측 CORS 설정의 문제라는 글도 몇몇 보았으나,, 알고보니 httpOnly 쿠키는 js 로 확인할 방법이 없다고 한다.

쿠키

쿠키는 클라이언트에 저장되며, Key와 Value로 이루어진 데이터 이다.
주로 쿠키는 서버에서 생성하여 Set-Cookie HTTP Response Header에 넣어 클라이언트에게 전달 하고, 클라이언트에서 직접 쿠키에 데이터를 저장 할 수도 있다.
쿠키는 사용자가 별다른 컨트롤 없이 Request시 자동으로 브라우저가 Request Header를 넣어서 서버에 전송한다.
쿠키는 만료기간을 설정할 수 있으며, 만료기간 전 까지 브라우저가 종료 되어도 삭제 되지 않는다.

https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Set-Cookie

쿠키의 전달 과정

클라이언트가 서버로 요청을 보냈을 때 서버는 요청에 대한 응답과 함께 Set-Cookie 헤더에 쿠키를 담아 보낼 수 있다.

이 때 쿠키의 키와 내용 뿐 아니라 특정 도메인 또는 쿠키의 지속시간을 명시할 수 있고 경로제한을 설정해 쿠키가 보내지는 것을 제한할 수도 있다.

응답과 함께 Set-Cookie 헤더가 전송되면, 이후 클라이언트는 해당 서버로 요청을 할때 서버가 이전에 저장했던 모든 쿠키를 함께 담아 회신한다.즉, 쿠키에 저장하면 브라우저가 서버에서 웹페이지를 요청할때 해당 페이지에 속하는 쿠키가 요청에 추가된다는 장점이 있다.

만료 시점 파악하기

처음에는 토큰이 만료되었다는 것을 알려면 일단 만료된 토큰으로 API 요청을 보낸 후 토큰이 만료되었다는 응답을 받아야만 토큰 재발급 요청을 보낼 수 있을 줄 알았다. 그러나 accessToken 저장시에 현재시점 + 토큰의 만료기간 으로 토큰의 만료시점을 저장해두면, 굳이 만료된 토큰으로 확인사살을 받지 않아도, 매 API요청 직전에 그 만료시점이 지났는지 판별하여 그 전에 새롭게 토큰을 발급 받는 것이 가능했다. 그리고 이를 위해 Axios interceptor 를 이용한다.

Axios interceptor

axios 라는 http 통신 라이브러리에는 interceptor 가 존재한다. 이는 http 통신을 하기 직전 함수를 실행하여 AxiosRequestConfig 를 수정할 수 있도록 한다. 즉 이걸 이용하여 API 요청을 보내기 직전에 토큰이 만료되었는지 확인하고 만료되었다면 저장된 refresh 토큰을 사용해서 새로운 Token을 발급받아서 토큰을 교체하는 작업을 수행하는 것이다.

  • axios.interceptors.request.use() 함수에 실행할 함수를 매개변수로 넘기면 정상적으로 interceptor 가 등록

코드

import axios, { AxiosRequestConfig } from "axios";
import Cookie from "js-cookie";
import AuthService from "../api/AuthService";
import { BASE_URL } from "../constants/Config";
import moment from "moment";

const refresh = async (config) => {
  //const refreshToken = Cookie.get("refreshToken");
  const expireAt = sessionStorage.getItem("expiresAt");
  let token = sessionStorage.getItem("accessToken");
  console.log("만료확인");

  // 토큰이 만료되었다면
  if (moment(expireAt).diff(moment()) < 0) {
    var body = {
      email: sessionStorage.getItem("email"),
    };

    console.log("토큰을 재발급합니다!");

    //재발급 요청
    const res = await axios.post(
      "https://lakku-lakku.com/api/v1/users/re-issue",
      body
    );
    console.log("재발급 성공", res.data.accessToken);
    sessionStorage.setItem("accessToken", res.data.accessToken);
    sessionStorage.setItem(
      "expiresAt",
      moment().add(3, "minute").format("yyyy-MM-DD HH:mm:ss")
    );
    config.headers["Authorization"] = `Bearer ${token}`; // 토큰 교체
  }

  return config;
};

const refreshErrorHandle = () => {
  //Cookie.remove("refreshToken");
};

export { refresh, refreshErrorHandle };

Ref

https://slog.website/post/10

profile
하루하루 나아가는 새싹 개발자 🌱

0개의 댓글