REST-API & Axios를 통해서 서버에 데이터를 요청하는 방식을 사용하면서 일어났던 에러나 개선점에 대해 회고를 작성하였다.
// src/commons/api/config.ts
import axios, { AxiosInstance } from "axios";
const config = {
backend: {
baseURL: process.env.NEXT_PUBLIC_BACKEND,
},
};
const server = config.backend.baseURL;
const api: AxiosInstance = axios.create({
baseURL: server,
withCredentials: true,
headers: {
"Content-Type": "application/json",
},
});
export default api;
위의 코드는 프로젝트에 적용시킨, axios를 사용해서 API요청을 할 때 default 설정을 적용시킨 것이다. 우선 백엔드 서버 주소를 .env 파일로 숨기고 baseURL 옵션에서 백엔드 서버 주소를 설정하였다.
withCredentials 옵션은 기본적으로 웹 브라우저에서 크로스 도메인 요청에 대해 인증 정보(cookies, HTTP 인증 등)를 자동으로 포함시키지 않는다. 구현한 프로젝트에서는 쿠키를 통해 accessToken, refreshToken을 받기 때문에 적용시켜주었다.
withCredentials 옵션을 true로 설정하면, 브라우저는 요청을 보낼 때 인증 정보를 자동으로 포함시킨다. 이렇게 하면 요청이 성공적으로 처리되는 경우 서버는 클라이언트의 인증 상태를 유지하고 인증 정보를 사용하여 사이트를 지속적으로 이용할 수 있게 된다.
"Content-Type": "application/json" 옵션에서 "Content-Type" 은 요청 본문의 데이터 형식을 명시적으로 지정하여 서버가 데이터를 올바르게 해석하고 처리할 수 있도록한다. 여기서 "application/json"은 JSON 형식의 데이터를 사용한다는 것을 서버에 알리는 헤더 값이다.
또한 프로젝트에서 image file 업로드 기능도 있기 때문에 그 API요청 함수에는 "multipart/form-data" 옵션을 적용하였다.
export const deleteUserBoard = async (
data: IDeleteBoardData
): Promise<void> => {
const { accessToken, id } = data;
try {
await api.delete(`/admin/boards/${id}`, {
headers: { Authorization: "Bearer " + accessToken },
});
} catch (error) {
if (axios.isAxiosError(error)) {
const { response } = error;
throw {
message: response?.data?.message || "Server Error",
status: response?.status || 500,
data: response?.data,
};
} else {
throw error;
}
}
};
위의 API요청은 관리자가 유저의 게시글을 삭제할 수 있는 API요청이다. axios.delete를 사용하지 않고 config.ts 에서 선언한 api 라는 이름으로 변경하고, 첫번째 파라미터에는 url이 들어가지만 default 설정으로 도메인 주소는 설정했으므로 path, query string 옵션만 설정해주면 된다.
이러한 default 설정은 나중에 값이 변경될 경우 default 설정에서 변경해 주면되니 일일이 api요청에서 값을 변경할 필요가 없기 때문에 편리하다.
로컬환경에서는 CORS 에러가 발생하지 않았지만 배포한 사이트에서는 발생하였다.
우선 cors 에러가 뭔지 다시 한번 정리하자.
CORS(Cross-Origin Resource Sharing) 에러는 웹 애플리케이션에서 보안 상의 이유로 동일한 출처(origin)가 아닌 다른 출처의 리소스에 접근할 때 발생하는 에러이다.
출처란, 프로토콜(http, https), 호스트(needromance.online), 포트 번호(:80, :433)를 기반으로 출처를 정의한다.
기본적으로 웹 브라우저는 보안 상의 이유로 동일 출처 정책(Same-Origin Policy)을 따른다. 이는 웹 페이지가 자신의 출처에서 가져온 리소스에만 접근할 수 있도록 제한하는 보안 메커니즘이다. 따라서 다른 출처의 리소스에 직접적인 접근이 제한되며, 이로 인해 CORS 오류가 발생하는 문제가 발생하는 것이다.
이전 경험에 의해서는 백엔드 측에서 다른 출처로부터의 요청을 허락하는 도메인을 명시적으로 적어주거나 와일드카드를 사용해서(별로 좋지 않은 방법이긴 하지만) 동일 출처 정책을 지켜 해결했다.
하지만 이번에는 백엔드에서 문제없이 해결했다고 하였다고 답변이 왔기에, 프론트에서도 무언가 해결할 수 있는 방법을 찾아보게 되었다. next.js에서 문제가 있을 수도 있고, 이전에 백엔드와 프론트엔드 양 측의 CORS 설정은 문제가 없었지만 AWS api gateway를 사용한 이유로 CORS 에러가 발생한 경험도 있었기 때문에 혹시나 다른 문제가 있을까 고민하고 해결방법을 여러 찾아보았다.
axios default 설정에 Origin: "도메인"
이런 형식으로 헤더에 직접 명시하는 방법을 사용해 보았다. 하지만 이 방법도 의미가 없는 부분이 백엔드 자체에서 요청을 보내는 도메인과 백엔드 서버의 도메인이 일치하는지 확인을 해야 하는데, 아무리 클라이언트 측에서 이러한 옵션을 보내준다 하더라도 확인하는 작업에 문제가 있다면 해결할 수 없는 방법이다.
클라이언트 - 서버 관계의 리소스 요청의 단계에서 가운데에 중계 역할을 하는 프록시 서버를 설정해서 CORS 에러를 막는 방법이 있다. 하지만 이러한 방법은
프론트-백엔드 간의 서로 설정해야되는 부분도 있고, 우리가 설계했던 통신 방법이 아니었기 때문에(proxy 서버 구축이나 설정에 있어 경험이 없어 시간이 부족할 것 같았다)
// next.config.js
const nextConfig = {
reactStrictMode: true,
compiler: { emotion: true },
async headers() {
return [
{
source: "/(.*)",
headers: [
{
key: "Access-Control-Allow-Origin",
value: "https://need-romance.site",
},
{
key: "Access-Control-Allow-Headers",
value:
"Origin, X-Requested-With, Content-Type, Accept, Authorization",
},
{
key: "Access-Control-Allow-Methods",
value: "GET, POST, PATCH, DELETE, OPTIONS",
},
],
},
];
},
module.exports = nextConfig;
next.config.js 파일에서 페이지의 HTTP 헤더를 정의하기 위한 함수를 설정할 수 있는 여러 기능들을 찾다 보니 요청을 보낼시에 헤더 설정을 적용할 수 있는 기능들이 있어 적용시켜 보았다.
적용할 파일의 범위를 설정하는데 모든 페이지에서 적용하도록 설정을 해 주었다.
해당 경로(모든 경로)에 적용할 헤더를 정의한다. CORS 관련 헤더인
Access-Control-Allow-Origin: 을 동일한 출처로 설정하고,
Access-Control-Allow-Headers: 에서 백엔드로 요청을 보낼 옵션들을 설정들을 적용시켰으며,
Access-Control-Allow-Methods: 프로젝트에서 사용한 get, post, patch, delete 옵션들을 설정해 보았다.
하지만 위에 설명한 내용들과 같은 원인으로 똑같은 증상이 발생하였다. 프로젝트에서 해결된 방법은 아니였지만 next.config.js 에서 내가 생각하지 못한 여러 기능들을 찾아 볼 수 있었던 좋은 경험이었다고 생각한다.
결국 문제를 프론트 측에서는 해결할 방법을 찾지 못해 백엔드 개발자에게 작성한 코드를 요청하였다. nest.js 기반으로 작성된 코드다.
보기에는 문제가 발생할 만한 부분은 찾기 어려웠지만, url 이 정상적으로 적혀 있는지, 다른 http 헤더 옵션들이 맞게 되어있는지 찾아보았다.
nest.js 기반 cors 에러를 검색하던 중, stackoverflow 에 한 해결방법을 보게 되었다.
origin: 값을 배열형태로 설정되어 있어 백엔드 개발자에게 origin 옵션 값을 변경해 달라 요청했고, 그 후에 정상적으로 배포된 사이트에서 cors 에러가 사라지게 되었다.
결국 프론트엔드 측에서 cors 에러를 해결할 수 있는 방법은 거의 없다고 느꼈다. 근본적으로 백엔드 측에서 origin 설정을 올바르게 하지 않으면 클라이언트 측에서 보안상 바로 데이터 요청 자체를 거부해버리기 때문이다.(SOP 정책 위반) 하지만, 이러한 문제를 해결하기 위해 프론트 측에서 CORS 에러에 가능한 대처할 수 있는 방법들도 여럿 찾아보게 되었고 백엔드 측에서 CORS 에러를 해결하기 위한 방법들도 같이 찾아보게 되어 좋은 경험이었다고 생각한다.
https://stackoverflow.com/questions/50949231/nestjs-enable-cors-in-production