Project/삐삐(BIBI: Best Interior)

[JWT] 토큰 인가 및 토큰 재발급 관련 트러블슈팅

2023. 9. 18. 01:27
목차
  1. 📝 배경
  2. 🚨 문제 상황 - (1). api 요청시 헤더에 엑세스토큰을 담아 보냇으나, 403에러 발생
  3. 🔍️ 원인 파악 
  4. 🔨 해결 방안 
  5. 🚨 문제 상황 - (2). 토큰 만료시 재발급 로직 구현
  6. 🔍️ 원인 파악 
  7. 🔨 해결 방안
  8. 🎉 JWT 토큰 인가 및 재발급 관련 코드 전체

📝 배경

  • 본 프로젝트 (삐삐:Best Interior)에서는 서버가 클라이언트를 인증하는 방식 중 하나인 JWT를 이용하여 로그인 기능을 구현하기로 함
  • 또한, 토큰은 로컬스토리지에 저장하는 방식을 택했으며, 이렇게 저장된 토큰은 매 요청 헤더에 담아 적절한 인증 과정을 거친 사용자인지 판단하는 인가(Authorization)를 구현함
  • 이를 위해, axios 통신시 interceptors 를 이용하여 헤더에 토큰 정보를 담아 보내는 token.js라는 컴포넌트를 만들었으며, token 컴포넌트를 활용하여 useAxios라는 커스텀훅을 만들어서  통신로직을 추상화하고 재사용성 및 코드 가독성을 향상시키고자 했음

 

🚨 문제 상황 - (1). api 요청시 헤더에 엑세스토큰을 담아 보냇으나, 403에러 발생

  • 로그인 성공 시 로컬스토리지에 토큰(access,refresh) 이  제대로 저장되었고, 해당 토큰을 헤더에 담아 보내는데 403 에러 발생.
  • 여기서 403에러는 유효한 토큰이 아닐 시 반환하는 에러코드임

헤더에 토큰을 담아 api요청을 보냈는데, 403 에러가 뜨는 상황

🔍️ 원인 파악 

  • 원인 파악을 위해 포스트맨에 토큰값을 넣어 요청한 결과 제대로 응답이 오는것을 확인하고, 프론트쪽 코드 문제라는것을 확인함
  • 보통 JWT나 Oauth 토큰 인증 타입은 Bearer token을  사용하는점 그리고, 요청 헤더의 필드값으로 Authorization을 사용해야 한다는점을 간과하였음
// 당시 작성 코드
// 토큰 정보를 헤더에 추가하는 함수
const addTokenToHeaders = (config) => {
const refreshToken = localStorage.getItem("refreshToken");
const accessToken = localStorage.getItem("accessToken");
if (refreshToken) {
config.headers["RefreshToken"] = refreshToken;
}
if (accessToken) {
config.headers["AccessToken"] = accessToken; // => Authorization 라는 필드명이 아닌, AccessToken이라는 필드명으로 토큰을 보낸것
}
config.headers["Content-Type"] = "application/json";
config.headers["ngrok-skip-browser-warning"] = "69420";
config.withCredentials = true;
  • 즉, 해당 문제의 원인은, 요청 헤더의 필드명 Authorization값으로 적절한 토큰의 타입을 명시를 하지 않아서, 서버쪽에서 해당 토큰을 유효한 토큰이라고 인식하지 못했었던 문제였음

 

🔨 해결 방안 

  • 요청 헤더에 적절한 필드명(Authorization)의 필드값으로 access토큰의 값을 넣어주었음
// 토큰 정보를 헤더에 추가하는 함수
const addTokenToHeaders = (config) => {
const accessToken = localStorage.getItem("accessToken");
if (accessToken) {
config.headers["Authorization"] = `Bearer ${accessToken}`; // 필드명 수정, 필드값에 토큰 인증 타입 지정
}
config.headers["Content-Type"] = "application/json";
config.headers["ngrok-skip-browser-warning"] = "69420";
config.withCredentials = true;
return config;
};

요청 헤더에 적절한 필드명과 필드값을 넣어줌으로써 인가 관련 403에러 해결

 

🚨 문제 상황 - (2). 토큰 만료시 재발급 로직 구현

  • 엑세스토큰 만료시 재발급하는 로직 구현중 발생한 에러
  • 엑세스토큰 만료(유효하지 않은 토큰)시 서버에서 403에러를 반환하고, 해당 에러 발생시 클라이언트에서 리프래시토큰을 보내면, 서버에서 유효한 리프래시토큰임을 확인한 경우 새로운 엑세스토큰과 리프래시토큰을 응답 헤더로 보내도록 이야기가 된 상황이었음
  • 사실 이부분은 Fe,Be 모두 처음 구현하려는 기능이다 보니, 각자 공부하고, 큰틀에서만 이야기가 된채로 작업을 하여 오류가 발생할수 밖에 없었던것 같음
  • 또한, 해당 파트는 담당자분이 바쁘셔서 제가 중간에 넘겨 받게된 파트였기 때문에, BE담당자분과 디테일한 부분까지 얘기해가며 해결할 수 있었음

 

🔍️ 원인 파악 

토큰 재발급 로직과 관련하여 다음과같은 문제점을 고려하여 새롭게 코드를 짜봄

  1. 토큰 만료시, 새로운 요청으로 보내는 헤더에 엑세스토큰과 리프래쉬토큰의 적절하지 않았던 필드명
  2. 토큰 만료시, 리프래시 토큰을 인증하는 별도의 api가 없고, 해당 요청보낸던 url, method, data(body)을 그대로 활용해야함
  3. 토큰 재발급시 다시 엑세스 토큰 헤더에 담아 같은 요청을 다시 보내야함.

 

 

🔨 해결 방안

 1. 토큰 만료시, 새로운 요청으로 보내는 헤더에 적절한 필드명과 필드값 적용

  • 엑세스토큰은 Authorization 필드값으로, 리프래시토큰은 Authorization-refresh 필드값으로 넣어줌
// 토큰 갱신 및 재시도 함수 (403에러 반환시 실행함수)
const handleTokenRefresh = async (error) => {
// 로컬스토리지에 저장되어있는 엑세스토큰과 리프레시토큰 추출
const accessToken = localStorage.getItem("accessToken");
const refreshToken = localStorage.getItem("refreshToken");
if (refreshToken) {
try {
// 요청보낸 메서드와 url, data(바디값) 저장
const { method, url, data } = error.config;
// 요청보낸 메서드, url, 바디값 및
// 추가로 헤더에 엑세스토큰과 리프레시 토큰을 전송하도록 config설정
const configParams = {
method,
url: `${process.env.REACT_APP_API_URL}${url}`,
headers: {
"Authorization-refresh": `Bearer ${refreshToken}`,
Authorization: `Bearer ${accessToken}`,
},
data,
};

 

  2~3. 토큰 만료시, 리프래시 토큰을 인증하는 별도의 api가 없고, 해당 요청보낸던 url, method, data(body)을 그대로 활용하여 다시 요청을 보냄

  • 위의 1과정을 통해 유효한 리프래시 토큰을 받을경우, 새로운 엑세스토큰과 리프래시토큰이 응답의 헤더로 받음
  • 새로운 토큰을 로컬스토리지에 저장한 뒤, 새로운 토큰을 사용하여, 이전(만료된 토큰을 통해 보냈던 요청) 요청을 그대로 서버로 전송
// 토큰 갱신 및 재시도 함수 (403에러 반환시 실행함수)
const handleTokenRefresh = async (error) => {
~~~~~~
// 설정한 config로 다시 서버로 요청을 보냄
const response = await axios(configParams);
// 성공적으로 응답시 새로운 엑세스토큰과 리프레시토큰을 로컬스토리지에 저장
const newAccessToken = response.headers.authorization;
const newRefreshToken = response.headers["authorization-refresh"];
localStorage.setItem("accessToken", newAccessToken);
localStorage.setItem("refreshToken", newRefreshToken);
// 헤더에 새로운 엑세스토큰을 포함하여 서버로 재요청
error.config.headers["Authorization"] = `Bearer ${newAccessToken}`;
return api.request(error.config);
} catch (err) {
// 응답없는경우 로그아웃
window.dispatchEvent(new Event("logoutEvent"));
console.log(err);
}
} else {
window.dispatchEvent(new Event("logoutEvent"));
console.log("리프레시토큰 유효하지 않습니다.");
}
};

 

토큰 만료시 403에러를 반환
403에러 반환시 리프래시토큰을 보낸뒤, 유효한 리프래시토큰일 경우 새로운 엑세스토큰과 리프래시토큰을 응답의 헤더로 받음

 

새로운 토큰을 서버로부터 받으면, 토큰 만료시 보냈던 요청에서 새로운 엑세스토큰으로만 바꾼뒤 동일한 요청을 보내고, 정상적으로 서버 응답을 받음

 

🎉 JWT 토큰 인가 및 재발급 관련 코드 전체

import axios from "axios";
// Axios 인스턴스 생성
const api = axios.create({ baseURL: process.env.REACT_APP_API_URL });
// 요청에 토큰을 추가하는 함수
const addTokenToHeaders = (config) => {
const accessToken = localStorage.getItem("accessToken");
if (accessToken) {
config.headers["Authorization"] = `Bearer ${accessToken}`;
}
config.headers["Content-Type"] = "application/json";
config.headers["ngrok-skip-browser-warning"] = "69420";
config.withCredentials = true;
return config;
};
// 토큰 갱신 및 재시도 함수 (403에러 반환시 실행함수)
const handleTokenRefresh = async (error) => {
// 로컬스토리지에 저장되어있는 엑세스토큰과 리프레시토큰 추출
const accessToken = localStorage.getItem("accessToken");
const refreshToken = localStorage.getItem("refreshToken");
if (refreshToken) {
try {
// 요청보낸 메서드와 url, data(바디값) 저장
const { method, url, data } = error.config;
// 요청보낸 메서드, url, 바디값 및
// 추가로 헤더에 엑세스토큰과 리프레시 토큰을 전송하도록 config설정
const configParams = {
method,
url: `${process.env.REACT_APP_API_URL}${url}`,
headers: {
"Authorization-refresh": `Bearer ${refreshToken}`,
Authorization: `Bearer ${accessToken}`,
},
data,
};
// 설정한 config로 다시 서버로 요청을 보냄
const response = await axios(configParams);
// 성공적으로 응답시 새로운 엑세스토큰과 리프레시토큰을 로컬스토리지에 저장
const newAccessToken = response.headers.authorization;
const newRefreshToken = response.headers["authorization-refresh"];
localStorage.setItem("accessToken", newAccessToken);
localStorage.setItem("refreshToken", newRefreshToken);
// 헤더에 새로운 엑세스토큰을 포함하여 서버로 재요청
error.config.headers["Authorization"] = `Bearer ${newAccessToken}`;
return api.request(error.config);
} catch (err) {
// 응답없는경우 로그아웃
window.dispatchEvent(new Event("logoutEvent"));
console.log(err);
}
} else {
window.dispatchEvent(new Event("logoutEvent"));
console.log("리프레시토큰이 유효하지 않습니다.");
}
};
// 요청 인터셉터 설정
api.interceptors.request.use(addTokenToHeaders);
// 응답 인터셉터 설정
api.interceptors.response.use(
async function (response) {
return response;
},
async function (error) {
if (error.response && error.response.status === 403) {
// 엑세스 토큰 만료 시 토큰 갱신 및 재시도
return handleTokenRefresh(error);
}
return Promise.reject(error);
}
);
export default api;

 

'Project > 삐삐(BIBI: Best Interior)' 카테고리의 다른 글

[최적화] Pagenation 구현, 정적리소스 서버 구축  (0) 2023.10.05
댓글, 답글 기능 구현 중 발생한 에러  (0) 2023.09.19
[AWS S3] 클라이언트 배포 시 404 에러  (0) 2023.09.17
  1. 📝 배경
  2. 🚨 문제 상황 - (1). api 요청시 헤더에 엑세스토큰을 담아 보냇으나, 403에러 발생
  3. 🔍️ 원인 파악 
  4. 🔨 해결 방안 
  5. 🚨 문제 상황 - (2). 토큰 만료시 재발급 로직 구현
  6. 🔍️ 원인 파악 
  7. 🔨 해결 방안
  8. 🎉 JWT 토큰 인가 및 재발급 관련 코드 전체
'Project/삐삐(BIBI: Best Interior)' 카테고리의 다른 글
  • [최적화] Pagenation 구현, 정적리소스 서버 구축
  • 댓글, 답글 기능 구현 중 발생한 에러
  • [AWS S3] 클라이언트 배포 시 404 에러
2워노
2워노
2워노
워노의 코딩 일기
2워노
전체
오늘
어제
  • 분류 전체보기 (40)
    • 코딩 (30)
      • 코드스테이츠 45기(FE) (18)
      • JavaScript (1)
      • React (5)
      • HTTP, 네트워크 (2)
      • 데일리 코딩 (2)
      • Next.js (2)
    • Project (8)
      • 삐삐(BIBI: Best Interior) (4)
      • 점메추(점심메뉴추천) (1)
      • 탑개미자원 (3)
    • UI, UX (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 코드스테이츠
  • JWT
  • Prettier
  • vercel
  • 리액트배포
  • next.js
  • 임시저장
  • 정적리소스
  • sop
  • 프론트엔드부트캠프
  • 호스팅케이알
  • Babel
  • 토큰
  • Prettier format on save
  • cloudtype
  • 환경변수
  • 나만의아고라스테이츠
  • 도메인연결
  • CORS
  • 404에러
  • my agroa states
  • 리프래시토큰
  • 무한스크롤
  • 한글도메인
  • 토큰재발급
  • JSX
  • useState
  • 비동기
  • å
  • 엑세스토큰

최근 댓글

최근 글

hELLO · Designed By 정상우.
2워노
[JWT] 토큰 인가 및 토큰 재발급 관련 트러블슈팅
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.