웹에서 로그인을 한 후 로컬스토리지에 저장된 jwt를 서버에 전송해서 올바른 토큰인지 정검한 뒤 응답을 하는 과정을 직접 간이 프로젝트를 만들어서 테스트 해 보았다.
우선 프론트 파일구조는 다음과 같다.
src/
├─ api/
│ ├─ apiClient.js // 서버1 axios 인스턴스 + 인터셉터 등록
│ ├─ api2Client.js // 서버2 axios 인스턴스 + 인터셉터 등록
│ ├─ api3Client.js // 서버3 axios 인스턴스 + 인터셉터 등록
│ └─ httpClients.js // clientKey로 axios 인스턴스 선택하는 라우팅 함수
│
├─ lib/
│ └─ axiosFunc.js //HTTP 공통 래퍼 함수
│
├─ App.jsx // 백엔드 호출버튼
├─ main.jsx // React 엔트리 파일
└─ config.js // url 환경설정값
*유지보수나 보안을 위해 .env 파일로 url을 빼는게 더 좋은 구조이다.
http get요청을 보내는 프론트의 프로세스를 그림으로 표현하자면

1) App.jsx에서 버튼 클릭 이벤트로 test401() 함수 실행
2) test401() 내부에서 getAxios() 공통 래퍼 호출
3) getAxios()가 getHttpClient()로 넘겨준 파라미터에 맞는 axios 인스턴스를 선택 (백엔드 서버가 다수라는 가정)
4) 선택된 axios 인스턴스가 있는 apiClient.js 내부에서 인터셉터 등록
5) axios 인스턴스가 config.js의 API_URL을 사용해 실제 백엔드에 요청 전송
일단 로컬스토리지에 아무 토큰을 저장해 주자.
localStorage.setItem("login-token", "Bearer demo-token234");
<App.jsx - test401함수>
// 401 테스트 → /secure (토큰 없거나 잘못되면 401)
const test401 = async () => {
try {
const res = await getAxios("/secure", {}, false, "API");
console.log("getAxios Response:", res);
} catch (e) {
console.error("401 발생 (getAxios + 인터셉터 작동)", e);
}
};
버튼을 클릭하면 이 함수가 실행되어 getAxios()를 호출한다.
파라미터는 엔드포인트, 쿼리스트링, 로딩 블러커 표시 여부, axios 인스턴스이다.
*로딩블러커란? API 요청이 끝날 때까지 화면 일부 또는 전체를 막고, 로딩 중이라는 표시를 보여주는 UI 장치
<Node.js 백엔드 서버 엔드포인트>
const VALID_TOKEN = "Bearer demo-token";
app.get("/secure", (req, res) => {
const authHeader = req.get("authorization") || req.get("Authorization");
if (!authHeader || authHeader !== VALID_TOKEN) {
return res.status(401).json({
success: false,
errorMessage: "Unauthorized: invalid or missing token"
});
}
res.json({ success: true, data: { secret: "Top secret data" } });
});
여기서 토큰이 일치하는지 검사한다.
<axiosFunc.js - getAxios함수>
// GET 요청을 공통으로 호출하는 함수 (클라이언트 키로 axios 인스턴스 선택, params 전송)
export const getAxios = async (url, paramData, isBlock = false, clientKey) => {
const axios = getHttpClient(clientKey);
let param = { ...paramData };
return (await axios.get(url, { isBlock, params: param })).data;
};
그냥 axios.get을 해도 되지만 공통래퍼가 있다고 가정했다.
<httpClients.js - getHttpClient함수>
// 모든 axios 인스턴스를 레지스트리에 등록
const clientRegistry = {
API: apiClient,
API2: api2Client,
API3: api3Client,
};
/**
* 원하는 axios 클라이언트를 key로 가져오는 함수
* @param {string} key
* @returns axiosInstance
*/
export const getHttpClient = (key = "API") => {
return clientRegistry[key] || clientRegistry.API;
};
프론트와 연동된 백엔드가 여러개라고 가정해서 만든 파일이다.
<apiClient.js 인터셉터>
import axios from "axios";
import { config } from "../../config.js";
import { isEmptyVar } from "../lib/dataFunc.js";
const apiClient = axios.create({ baseURL: config.API_URL });
// 최종 JWT 저장 방식(localStorage / cookie / sessionStorage)에 따라 인증 인터셉터 로직 수정 필요
apiClient.interceptors.request.use(
(config) => {
if (localStorage.getItem("login-token") != null) {
config.headers["Authorization"] = localStorage.getItem("login-token");
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
apiClient.interceptors.response.use(
async (config) => {
return config;
},
async (err) => {
if (err.response.status === 401) {
localStorage.removeItem("login-token");
document.location = "/Login?token=reset";
} else {
const error = err.response.data;
if (!error.success) {
let msg = isEmptyVar(error.errorMessage)
? `통신 중 오류가 발생 하였습니다. 담당자에게 전달 바랍니다. code : ${err.response.status}`
: error.errorMessage;
console.error("API Error:", msg);
}
}
return Promise.reject(err);
}
);
export default apiClient;
인터셉터의 역할은 요청마다 자동으로 JWT 토큰을 실어 보내기 위해 localStorage에 "login-token"이 있으면 해당 값을 Authorization 헤더에 자동으로 추가하는 것이다.
즉, 매번 /secure 요청할 때마다 직접 헤더를 붙일 필요 없어진다.
<config.js>
//dev OR QA
const DEV = {
API_URL: "http://localhost:4000",
GIS_URL: "",
LENS_URL: "",
};
// real
const REAL = {
API_URL: "",
GIS_URL: "",
LENS_URL: "",
};
export const config = DEV;
백엔드 url 하드코딩.
<백엔드 api 호출 성공결과>

<백엔드 api 호출 실패결과>

'React' 카테고리의 다른 글
| [React] 제어 컴포넌트와 비제어 컴포넌트 (Controlled Component, Uncontrolled Component) (0) | 2026.02.02 |
|---|---|
| [@mui/x-data-grid-premium] 계층구조 설계 예시 (0) | 2025.12.22 |
| [React] useReducer, memo, useMemo (0) | 2025.11.20 |
| [React] axios / fetch / Express의 app.get() 역할 구분 (0) | 2025.11.18 |
| React를 사용할 때 알면 좋은 훅 정리 (1) | 2025.09.01 |