에러 해결 - 23. 401 Unauthorized, 405 Method Not Allowed, vercel 배포 문제.

이유승·2023년 8월 4일
0

에러 해결

목록 보기
23/25
post-custom-banner

원티드에서 주최하는 8월 프론트엔드 인턴십신청을 위해 사전과제를 만들던 와중에 다음과 같은 에러를 경험하였다.



1. TODO LIST.

본 사전 과제는 회원 가입을 완료하면 서버에 데이터가 저장되고, 로그인을 하게 되면 서버에서 사용자가 입력한 아이디와 비밀번호를 확인한 뒤 서버에 존재하는 사용자가 확인되면 JWT 토큰을 반환하는 구조로 이루어져 있다.

프론트에서는 이 토큰을 웹 스토리지에 저장하여 사용자가 로그인을 하였는지 확인하는데 사용하고, 사용자가 로그인을 하였다고 확인될 경우에는 홈페이지에서 TODO LIST 페이지로 전환되도록 구현하였다.

사전 과제 프로젝트의 메인 화면. 사용자는 계정이 있으면 로그인, 없으면 회원 가입 이후 로그인하게 된다.

로그인한 사용자는 로그인/회원가입 페이지에서 TODO LIST 페이지로 이동한다. 로그인을 한 상태에서는 사용자는 로그인/회원가입 페이지로 진입할 수 없다. (리다이렉트 기능을 적용하여 페이지를 강제로 이동시킨다.)

TODO LIST 페이지는 사용자가 자신의 TODO를 추가, 수정, 삭제할 수 있는데 이 데이터들은 모두 서버에서 관리한다.



2. 401 Unauthorized.

그런데 TODO LIST 페이지에서 수행해야하는 TODO 조회 기능에서 401 에러가 발생하였다! 401 에러는 다음과 같은 이유로 발생된다.

요청된 리소스에 대한 유효한 인증 자격 증명이 없다.

문제는 내가 구현한 기능에서는 정상적으로 로그인도 이루어졌고, 서버에서 토큰도 제대로 반환되었고, 반환된 토큰이 분명 웹 스토리지에 저장되어 있음에도 401 에러가 발생하고 있다는 것. 나를 더욱 혼란스럽게 하는 것은 401 에러가 계속 발생하는게 아니라 가끔씩 발생한다는 점이었다.

아예 발생하지 않던가, 계속 발생하던가도 아니고 발생 할 때도 있고 아닐 때도 있어서 골머리를 썩혔는데.. 원인을 파악하다가 하나의 가능성이 떠오르게 되었다.

import axios from 'axios';

const token = localStorage.getItem('token');

const header = {
    headers: {
        Authorization: `Bearer ${token}`,
        'Access-Control-Allow-Origin': '*',
        'Content-Type': 'application/json',
    },
};

// todo 조회.
const getTodo = async () => {
    return await axios.get(`${process.env.REACT_APP_API_URL}todos`, header);
};

백엔드 기능을 구현할 때 위와 같이 헤더를 미리 설정해두고 URL과 함께 axios의 인자로 사용했는데, 비동기 통신의 특성상 웹 스토리지에서 토큰이 제대로 받아오기 전에 axios 통신이 먼저 출발해서 발생하는 문제가 아닌가 하는 생각이 들었다. 자바스크립트에서 비동기 통신 관련으로 흔하게 발생하는 문제이고, 401 에러가 때에 따라서 간헐적으로 발생하는 것을 보아 이쪽이 원인이 아닌가 싶었다.

그리하여 여기저기 알아 본 결과, 내가 작성한 백엔드 코드에 상당한 문제가 있었다는 사실을 알게되었다.

import axios from "axios";

// Axios를 사용하여 API와 통신할 수 있는 클라이언트를 생성.
export const apiClient = axios.create({
    // API 요청을 보낼 때 기본적으로 사용할 URL.
    // 이전 버전처럼 이 부분이 하드코딩되어있을 경우 배포 환경에서 기능이 정상동작하지 않을 수 있다.
    // 
    baseURL: process.env.REACT_APP_API_URL,
    headers: {
        // CORS(Cross-Origin Resource Sharing) 정책을 우회하기 위해 모든 출처(origin)에서의 요청을 허용합니다. 이렇게 하면 브라우저에서의 클라이언트 사이드 호출에 제한을 두지 않습니다. 이 헤더를 서버 측에서 설정해야 실제로 CORS를 허용할 수 있다.
        'Access-Control-Allow-Origin': '*',
        // 요청 데이터의 타입을 JSON으로 지정합니다. API 서버에 JSON 형식의 데이터를 전달할 때 유용합니다. 이 헤더를 설정하면 서버가 요청 본문을 JSON으로 파싱할 수 있게 된다.
        'Content-Type': 'application/json',
    },
});

// 요청을 인터셉트하고 수정하는 함수.
// 요청이 실행되기 '전'에 호출.
apiClient.interceptors.request.use((config) => {
    const token = localStorage.getItem('token');

    // Axios 인스턴스의 인터셉터를 이용하여 요청 전에 헤더에 Authorization 필드를 추가.
    // 로컬 스토리지에 access_token이 있고 요청 객체에 헤더가 설정되어 있을 때만 코드가 실행되도록 한다.
    if (token && config.headers) {

        // 요청 헤더에 Authorization 필드를 추가.
        // 액세스 토큰이 포함되며, 액세스 토큰은 Bearer 스키마를 사용하여 설정.
        // Bearer? HTTP 인증 헤더에서 사용되는 일반적인 방법.
        config.headers['Authorization'] = `Bearer ${token}`;
    };

    // 이렇게 설정된 헤더는 해당 Axios 인스턴스를 사용하여 API 요청을 보낼 때마다 자동으로 요청에 추가되어 서버로 전달.
    // API 서버는 이를 통해 클라이언트가 인증된 사용자인지 확인.
    // 사용자가 로그인한 상태에서 API 요청을 보낼 때, 이 코드를 통해 액세스 토큰이 포함된 헤더가 자동으로 생성되어 인증이 이루어지게 된다.

    return config;
});

Axios를 사용할 때는, 내가 구현한 것처럼 개별 기능 단위에서 헤더만을 추가해서 바로 axios를 호출해서는 안된다.

우선 Axios의 create 함수를 사용하여 API와 통신할 수 있는 클라이언트를 먼저 만들어주어야 한다. 이 곳에서 호출이 이루어질 baseURL과 headers를 설정해주고, Axios interceptor를 이용하여 요청이 실행되기 이전에 먼저 수행해야하는 작업을 작성해주어야 한다.

이렇게 되면 Axios 호출이 이루어지기 전에 웹 스토리지에 있는 토큰을 먼저 가져오기 때문에 401 에러가 발생하지 않게 된다. 코드를 수정하고 기능을 테스트해보니.. 잘 동작한다. 더 이상 401 에러가 발생하지 않는다!



3. vercel 배포 문제.

완성된 사전 과제는 동작 영상을 촬영하거나, 외부에 배포해야 한다. 영상 촬영은 경험도 기술도 수단도 없으니 배포 방식을 선택하였다.

이전까지는 파이어베이스를 사용하여 배포했었는데, 백엔드를 맡길 것도 아니고 개발자들이 프로젝트를 배포하는데 파이어베이스를 많이 이용하는 것 같지 않아서 다른 수단을 알아보았다.

나는 이전에 Git Page나 Heroku를 사용했었는데, Git Page는 배포가 그렇게 간단하지 않았고 Heroku는 작년부로 유료화되어 더 사용하기가 어려웠다. 그래서 이번에는 무료에 배포가 아주 간단하다는 vercel을 사용해보았다.

  • 프로젝트 배포에 대한 내용은 나중에 단독 포스트에서 다룰 예정이다..

vercel에 git 계정을 연결하고, 사전과제 Repo를 import하여 빌드와 배포가 완료되었는데..?

아무 것도 출력되지 않는다. 아직 파일 업로드가 완전하지 않던지, 통신 문제로 시간이 걸리는 것일 수도 있어서 기다려보았는데 30분 넘도록 이 하얀 화면에서 바뀌는게 없다!

로컬에서는 아무 문제가 없었고, vercel에서 배포할 때 설정이 잘못되었는지도 확인해보았는데 배포가 정상적으로 이루어지지 않고 있었다.

이것도 해결하는데 시간이 좀 소모되었지만.. 원인은 다음과 같았다.

package.json에서 homepage 속성을 사용하여 프로젝트 시작시 최초 URL을 변경한 것.

사전 과제의 최초 시작 페이지는 로그인 페이지가 되어야하고 로그인 페이지의 url은 /signin이어야 한다. 그런데 기본 설정은 /으로 되어 있으니 이걸 homepage 속성을 이용해 변경한건데 로컬 환경에서야 문제가 없지만 배포 환경에서는 어딘가에서 충돌을 일으킬 소지가 있어 정상 배포가 되지 않는 것이다. 따라서 이 부분은 homepage 속성을 삭제하고, App.js에서 Navigate 컴포넌트를 이용하는 방식으로 변경하였다. 그리고 다시 재배포하니.. 화면에 정상적으로 출력된다.



4. 405 Method Not Allowed.

그리고 배포 환경에서 기능 테스트를 진행하는데.. 이번에는 405 에러가 발생하면서 모든 기능이 동작하지 않았다. 이건 완전 생소한 에러라서 원인을 찾아봤는데..

서버가 요청 메서드를 알고 있지만 대상 리소스가 이 메서드를 지원하지 않음.

당최 이게 무슨 뜻인지 이해하기가 어렵다. 그래서 다른 자료를 찾아봤는데, 대략 axios 호출 url 혹은 매칭된 메서드가 잘못되었을 때 발생하는 에러라고 한다.

그래서 배포 환경에서 개발자 도구로 네트워크의 요청란을 잘 살펴보니.. axios의 호출 url이 완전히 잘못되어 있다는 사실을 확인하였다.

(...) pre-onboarding-selection-task.shop/todos

문제가 발생하지 않은 로컬 환경에서는 위와 같이 내가 구현한 대로 url이 잘 들어가 있었다. 그런데 vercel 환경에서는..

(...) lystodoappako.vercel.app/'pre-onboarding-selection-task.shop/todos'

멀쩡한 url을 왠 ''가 감싸고 있는데다가 vercel의 배포 주소가 앞에 추가되면서 완전히 이상한 url이 사용되고 있었다!

이 녀석도 시간이 좀 걸리긴 했지만, 2번 문단의 401 Unauthorized 에러를 해결하면서 같이 해결할 수 있었다.

// todo 조회.
const getTodo = async () => {
    return await axios.get(`${process.env.REACT_APP_API_URL}todos`, header);
};

기존 코드에서는 환경 변수로 적용한 API url에 기능 호출에 필요한 키워드를 백틱을 이용해 하나로 합쳐서 사용하도록 하였다.

url에 ''가 감싸져 있는 것은 이 백틱이 로컬 환경과 달리 vercel 환경에서 정상적으로 적용되지 않는다는 것. vercel의 배포 주소가 url 앞에 추가되는 것은 무엇이 원인인지 모르겠지만 일단 401 에러를 해결하면서 axios의 인자로 들어갈 호출 url의 구조를 다르게 바꾸어보았다.

export const apiClient = axios.create({
    baseURL: process.env.REACT_APP_API_URL,
    headers: {
        'Access-Control-Allow-Origin': '*',
        'Content-Type': 'application/json',
    },
});

(...)

const getTodo = async () => {
    return await apiClient.get('todos');
};

api 호출이 이루어지기 에, apiClient를 생성하고 baseURL 또한 미리 설정하도록 구현하였다. 기능을 테스트 해보니.. 로컬 환경 및 vercel 환경 모두에서 배포가 문제가 없이 이루어지고 기능도 정상적으로 동작한다. 해결 완료!



5. axios... 배워야 한다.

예전에 사이드 프로젝트를 하나 진행하면서 사용해본 적은 있었지만, 그 당시에는 외부 DB에 저장된 데이터를 받아오기만 하는 기능에 불과하다보니 이번 문제와 같은 상황이 발생하지 않았다.

게다가 최근 진행한 프로젝트 2개 모두 백엔드와 DB를 파이어베이스로 구현하다보니 axios를 사용할 일이 없어 이번 사전과제를 구현하면서 생각보다 더 많은 문제에 봉착했고, 해결하는 것도 시간이 걸렸다고 생각한다.

취직을 앞두고 자바스크립트 복습에 타입스크립트 공부에 리액트와 리덕스까지 섭렵하느라 정신이 없지만, axios에 대해 짧게라도 제대로 학습할 필요성을 깨달았다.

profile
프론트엔드 개발자를 준비하고 있습니다.
post-custom-banner

0개의 댓글