본문 바로가기
Develop/React

react, axios를 사용한 token 인증 기능 개발 (2)

by J0DEV 2022. 9. 4.
반응형

React 프로젝트에 Axios Interceptor 를 이용하여 Token 인증 기능을 개발해보자.

 

 

프로세스

1. POST /api/v1/auth/login 로그인 요청
2. 로그인 성공 시, 서버로부터 Access, Refresh Token을 Response로 전달 받고 해당 토큰을 로컬 스토리지에 저장
3. 인증이 필요한 API 요청 시, header에 Access Token을 포함하여 요청
4. 만약 http status code 401 (unauthorized) 가 반환되면 이전 요청을 가지고 있고,
5. refresh 토큰을 이용하여 POST /api/v1/auth/token/refresh 요청 후
5. 성공적으로 Token이 갱신되면 갱신된 Access, Refresh Token을 Response로 전달 받고 로컬 스토리지 내의 토큰과 교체
5-1. 실패하면 로그아웃 처리
6. 이전 요청의 헤더의 토큰을 갱신된 토큰으로 변경한 후, 재전송

여기서 살펴볼 것은 2번 부터 5번 과정이다.

 

시작하기 앞서

우선 우리가 웹 서비스를 사용할 때 요청은 크게 두가지로 나뉜다.

1. 인증이 필요한 요청

2. 인증이 필요없는 요청

 

1. 인증이 필요한 요청이란

쉽게 말하면 로그인이 필요한 요청이다.

좀더 상세히 설명하면 요청을 보낼때 해당 유저가 서비스에 가입되고, 인증된 유저이어야하며 이를 증명할 수 있는 인증 정보를 전송한다. (또는 서버에서 가지고 있다. 토큰을 이용한 stateless한 환경에서는 해당 정보를 클라이언트 즉 유저가 가지고 있다.)

예를 들어 "나의 정보 확인" 기능을 제공할 때, 여기서 "나"가 누군지 서버는 모른다. "나"가 누구인지 그리고 정말 "나" 가 맞는지를 서버에 알려주어야 한다.

풀어서 설명하면 우리가 다른 나라에 입국하려 할때,
어떤 목적을 가진 누구이고, 입국해도 되는가를 판단하는 입국 심사과정을 거친다.
이때 일반적으로 여권을 가지고 누구인지 왜 입국하는지를 판단하며 여기서 여권이 바로 인증 정보라고 할 수 있다.

 

2. 인증이 필요없는 요청이란

로그인이 필요없는 요청이다.

즉 인증정보가 필요없다. 누구에게든 공개되어 있고 내가 누구인지 별도의 검증이 필요없는 요청이다.

따라서 서버에게 "나"가 누구인지 알려줄 필요가 없다.

 

Axios

이제 이를 기준으로 Axios 인스턴스를 만들 것이다.

아래와 같이 두개의 Axios 인스턴스 코드를 작성해준다.

// init.js
export const req = axios.create({
    // 비 로그인 상태 
    timeout: 60,
    withCredentials: false, // 쿠키를 사용하지 않기 때문에 false
    headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
    }
});

export const authReq = axios.create({
    // 로그인 상태
    timeout: 120,
    withCredentials: false, // 쿠키를 사용하지 않기 때문에 false
    headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
    }

withCredntials와 Access-Control-Allow-Origin 의 경우 서버가 어떻게 구성되어 있는지, 토큰의 저장 위치는 어딘지에 따라 다르다.

보통은 cookie로 토큰을 관리하는 지에 따라 조금씩 달라진다.

 

req 인스턴스는 로그인 하지 않은 상태, authReq는 로그인한 상태에서 사용할 것이다.

 

이후 authReq에 interceptor 기능을 이용한다.

 

우선 request interceptor 를 먼저 작성해준다.

// init.js
authReq.interceptors.request.use(
    config => {
        // 토큰 확인
        const token = localStorage.getItem("access_token");	// 로컬 스토리지에서 access_token 조회
        config.headers.Authorization = `Bearer ${token}`; // 조회된 access_token을 헤더에 넣어줌

        return config;
    },
    error => Promise.reject(error)
);

위 코드가 하는 역할은 인증 요청을 보내기 전, access_token을 헤더에 넣어주는 것이다.

(분만 아니라 config를 새로 설정할 수 있다.)

 

이후 response interceptor를 작성한다.

//init.js
authReq.interceptors.response.use(
    response => response,
    async err => {
        const originalReq = err.config;
        if (err.response.status === 401 && err.config && !err.config.__isRetryRequest) {
            // 401 이고, err.config가 있고, retry reqeust가 아니면 토큰 만료라고 판단하여 refresh 요청
            originalReq._retry = true;
            // 만약 retry인 request가 401일 되면 위의 분기를 통해서 refresh하지 않는다.
            try {
                const res = await axios.post(`${auth_url}/api/v1/auth/token/refresh/`, {
                    refresh_token: localStorage.getItem("refresh_token")
                });
                const data = res.data;

                localStorage.setItem("acccess_token", data.data.access_token);
                localStorage.setItem("refresh_token", data.data.refresh_token);

                originalReq.headers.Authorization = `Bearer ${data.data.access_token}`;

                return axios(originalReq);
            } catch (error) {
                // refresh 토큰도 만료되었거나 오류 발생 시
                window.location.href = '/auth/logout';
                
                return Promise.reject(err);
            }
        }
        return Promise.reject(err);
    }
);

우선 토큰이 만료되었거나 올바르지 않으면 서버에서 401 status를 보내준다고 가정한다.

이후, retry 상태인 요청이었는지 판단한다.

 

토큰을 갱신하는 api를 요청하여 성공적으로 토큰이 갱신되면 localstoage에 토큰 갱신해주고 

직전에 보냈던 요청의 헤더를 변경해서 재전송해준다.

 

다음 글에서는 간단한 웹사이트 및 인증 서버를 만들어 정상적으로 axios 코드가 동작하는지 테스트 해보자.

반응형