🍪 불필요한 토큰 호출 방지

개요

  • 토큰이 유효시간을 가지고 있어 쿠키에 담아 보관하고 있음에도 페이지 진입 시 새로운 토큰 값을 호출하고 있고, 같은 API를 사용하는 페이지 진입 시에도 새로운 토큰 값을 호출하고 있는 문제 발견.

토큰을 쿠키에 담은 이유
일반적으로 토큰을 저장하는 저장소는 localStorage와 cookie 가 있다.
쿠키를 선택한 이유는 XSS 공격에 취약한 localStorage 보다 상대적으로 안전해서 선택하게 되었다.

Cookie VS LocalStorage

분석

  • 로직의 문제

최초 로직

  1. 책 찾기 페이지 로드 시 토큰 값 호출하는 API 통신
  2. 호출된 값을 쿠키에 저장
  3. 쿠키에 토큰이 있으면 쿠키에 담겨 있는 토큰 값 return
  4. 쿠키에 토큰이 없으면 토큰 값 호출하는 API 호출하여 토큰 값 return

토큰이 유효시간을 가지고 있기 때문에 쿠키에 담아 토큰이 있는지 없는지를 유무 파악하는 것인데, 위 방식대로 하면 쿠키에 토큰 유무를 파악하는 로직이 토큰 API 호출을 한 후에 있으니, 토큰 유효시간이 만료가 되지 않았음에도 불구하고 토큰을 호출하고 있어서 서버와의 불필요한 통신을 하고 있으며, 쿠키에 담아 토큰의 유무를 파악하는 로직 자체가 쓸모가 없는 코드였다. 그냥 흐름 자체가 엉망이였다.

해결 방법

  • 로직 수정

로직 수정

  1. 페이지 진입 시 쿠키에 토큰 값이 있는지 확인.
  2. 쿠키에 토큰이 있으면 쿠키에 저장된 토큰 값 return
  3. 쿠키에 토큰이 없으면 토큰 값 호출하는 API 통신 후 쿠키에 저장하고, 쿠키에 저장된 토큰 값 return

→ 쓸모 없는 로직을 줄이기 위해 진입부터 쿠키에 토큰이 있는지를 확인하는 로직으로 변경했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 토큰 호출하는 API
const tokenAPI = async () => {

    return await fetch('/api/v1/millie/oauth2/token.json', {
        method: 'POST',

    }).then(response => response.json())
        .then((result) => result.access_token);
}

// 토큰을 cookie에 저장
const saveToken = (tokenResponse) => {
    const token = tokenResponse;

    const expiresDate = new Date();
    expiresDate.setMilliseconds(270000);

    const cookieString = `token=${token}; path=/; Max-Age=240;`;

    return document.cookie = cookieString;
}

// 토큰이 있는지 확인
const checkToken = async () => {

    const cookies = document.cookie.split(';');

    let tokenValue;

    for (let i = 0; i < cookies.length; i++) {
        const cookieValue = cookies[i].trim();

        // cookieValue에 token이 있고 || 'token=' 이후의 값을 찾음
        if(cookieValue.startsWith('token=')) {
            tokenValue = cookieValue.substring('token='.length);
        }
    }

    if (tokenValue === null || tokenValue === '' || typeof tokenValue === 'undefined') {
        tokenValue = await tokenAPI();
        saveToken(tokenValue);
    }

    return tokenValue;

}

Class 문법으로 리팩토링

다채움 작업을 시작할 때 담당님께서 보안을 중점으로 둔 ‘시큐어코딩’이라는 것을 알려주셨다.
보안 측면에서 안전한 코드를 만들 수 있는 7개의 유형 중 ‘캡슐화’ 를 하기 위해 공부 차원으로 Class 문법으로 리팩토링 해보았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class GetToken {
    async getToken() {
        const tokenAPI = async () => {
            return await fetch('/api/v1/millie/oauth2/token.json', {
                method: 'POST',

            })
                .then(response => response.json())
                .then((result) => result.access_token);
        }


        const saveToken = (tokenResponse) => {
            const cookieString = `token=${tokenResponse}; path=/; Max-Age=240;`;

            return document.cookie = cookieString;
        }


        const checkToken = async () => {

            const cookies = document.cookie.split(';');

            let tokenValue;

            for (let i = 0; i < cookies.length; i++) {
                const cookieValue = cookies[i].trim();

                if(cookieValue.startsWith('token=')) {
                    tokenValue = cookieValue.substring('token='.length);
                }
            }

            if (tokenValue === null || tokenValue === '' || typeof tokenValue === 'undefined') {
                tokenValue = await tokenAPI();
                saveToken(tokenValue);
            }

            return tokenValue;

        }


        return await checkToken();
    }
}

const GET_TOKEN = new GetToken();

결과

로직 변경으로 불필요한 서버의 호출이 대폭 감소했다. 유효한 시간이 있음에도 제대로 활용하지 않고, 쿠키에 저장만 한다고 끝이 아니라는 것을 알게 되었다. 변경 전 코드와 변경 후 코드를 두고 비교를 해보았다. 불필요한 순서였는가 아니면 필요한 순서였는가. 흐름을 잘 생각하고 코드를 작성해야겠다.