[React] OAuth 2.0 사용하기 - refresh token grant

강버섯·2022년 2월 10일
5

AUTHORIZATION

목록 보기
4/9

👉 refresh token

REFRESH_TOKEN은 사용자가 인증을 받을 때 부여받는 token 중 하나로, ACCESS_TOKEN이 만료될 경우 새로운 ACCESS_TOKEN을 받아오기 위해서 사용한다.

ACESS_TOKEN이 만료되었는가를 확인하는 방법은,

  1. 일단 보내고 오류가 나면 만료가 된 것으로 판단
  2. ACESS_TOKEN 의 만료기한을 storage에 같이 저장해두고 판단

등이 존재한다.

👉 refresh token을 이용해 access token 받아오기

REFRESH_TOKEN을 이용해 ACCESS_TOKEN을 받아올 때에는 token을 받아왔던 TOKEN_ENDPOINT

  1. grant type을 "refresh_token"으로 설정
  2. 발급받아 놓은 REFRESH_TOKEN을 담아서

요청을 보내주면 새로운 ACCESS_TOKEN을 받을 수 있다.

token 👇

const {refresh_token} = req.body;
// basic 인증으로 보내기
const basicHeader = Buffer.from(
  `${OAUTH_CLIENT_ID}:${OAUTH_CLIENT_SECRET}`
).toString("base64")
const headers = {
  "Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
  Authorization: `Basic ${basicHeader}`
};

const resp = await axios.post(
  OAUTH_TOKEN_ENDPOINT,
  qs.stringify({
    grant_type: "refresh_token",
    refresh_token,
  }),
  {
    headers
  }
);
res.status(200).json(resp.data);

👉 axios interceptor

Token 갱신을 위해서는 위에서 말했듯이 REFRESH_TOKEN을 인증 요청에 붙여서 보내야한다.
매번 ACCESS_TOKEN의 만료를 판단하고 만료됐을 경우 REFRESH_TOKEN을 통한 재요청을 하기에는 번거로우니 작업의 자동화를 하면 편할 것이다.
이런 token을 항상 자동으로 붙여주는 방법은,

  1. 요청마다 header에 token을 붙이는 방법
  2. 오류가 발생할 경우 token을 붙이는 방법

으로 해볼 수 있다.

token refresh를 진행해보기 위해서 axios interceptor를 사용해본다.
axios interceptor를 사용하면

  1. 요청을 보내기 전
  2. 응답이 then, catch로 처리되기 전

에 가로채서 실행을 시킬 수 있다.
요청을 보내기 전에 항상 ACCESS_TOKEN을 붙여주거나 오류가 발생시에 REFRESH_TOKEN을 이용한 ACCESS_TOKEN 재요청을 시켜줄 수 있다는 이야기.

다만 모든 요청에서 동일한 작업을 필요로 하는 것은 아니기 때문에 api 호출을 변수값으로 설정해두고 window를 사용해 값을 저장한 다음, 브라우저에서 일어나는 일들은 window의 값을 참조하게하는 방식으로 진행했다.
따라서 특정 주소로 보내지는 api 요청에서만 interceptor가 실행이 된다.

if (typeof window !== "undefined"){
  //interceptor 실행 내용
}

✏️ 항상 ACCESS_TOKEN 붙여 요청 보내기

  • 요청이 보내지기 전 interceptor 설정
    axios.interceptors.request.use를 사용하면 요청이 보내지기 전에 요청을 가로채서 원하는 작업을 하도록 설정할 수 있다.
axios.interceptors.request.use(
  (cfg) => {
    // 요청을 보내기 전에 수행할 일
    return cfg;
  }, (error) => {
    // 오류 요청을 보내기 전에 수행할 일
    return Promise.reject(error);
  });

interceptors.js 👇

// axios로 요청을 보내기 전 intercept
axios.interceptors.request.use((cfg) => {
  // 특정 api endpoint일 때만 token을 붙이도록 설정
  if (cfg.url.startsWith(window.API_ENDPOINT)) {
    // request를 보낼 때 access token을 자동으로 붙이도록 함
    cfg.headers.authorization = `Bearer ${localStorage.getItem("access_token")}`;
  }

  return cfg;
});

✏️ ACCESS_TOKEN 만료로 인한 에러 발생 시 REFRESH_TOKEN을 이용해 token 재요청하기

  • then/catch가 실행되기 전(=응답을 처리하기 전) interceptor 설정
    axios.interceptors.response.use를 사용하면 요청 완료 후 응답을 처리하기 전에 가로채서 원하는 작업을 수행하도록 할 수 있다.
axios.interceptors.response.use(
  (response) => {
    // 응답 데이터 처리
    return response;
  }, (error) => {
    // 오류 응답 처리
    return Promise.reject(error);
  });

interceptors.js 👇

// axios로부터 response를 받아 처리하기 전에 intercept
axios.interceptors.response.use(
  (response) => {
    return response;
  }, (error) => {
    // res에서 error가 발생했을 경우 catch로 넘어가기 전에 처리하는 부분
    let errResponseStatus = null;
    const originalRequest = error.config;

    try {
      errResponseStatus = error.response.status;
    } catch (e){

    }

    // access token이 만료되어 발생하는 에러인 경우
    if ((error.message === "Network Error" || errResponseStatus === 401) && !originalRequest.retry) {
      originalRequest.retry = true;
      const preRefreshToken = localStorage.getItem("refresh_token");
      if (preRefreshToken) {
        // refresh token을 이용하여 access token 재발행 받기
        return axios.post(
          "/api/oauth/token",
          {
            grant_type: "refresh_token",
            refresh_token: preRefreshToken
          }).then((res) => {
          const {access_token, refresh_token} = res.data;
          // 새로 받은 token들의 정보 저장
          localStorage.setItem("access_token", access_token);
          localStorage.setItem("refresh_token", refresh_token);

          originalRequest.headers.authorization = `Bearer ${access_token}`;
          return axios(originalRequest);
        }).catch(() => {
          // access token을 받아오지 못하는 오류 발생시 logout 처리
          localStorage.removeItem("access_token");
          localStorage.removeItem("refresh_token");
          localStorage.removeItem("profile");
          window.location.href = "/";

          return false;
        });
      }
      // 오류 발생 시 오류 내용 출력 후 요청 거절
      return Promise.reject(error);
    }
    // 오류 발생 시 오류 내용 출력 후 요청 거절
    return Promise.reject(error);
  });
profile
무럭무럭 버섯농장

0개의 댓글