SPA에서 안전하게 쿠키(Cookie) 사용하기

gompro·2019년 12월 8일
6
post-thumbnail
post-custom-banner

들어가며

쿠키(Cookie)는 웹에서 빼놓을 수 없는 존재입니다. 가장 기본적인 인증에서부터 장바구니, 홍보, 유저 추적(Tracking) 등 여러가지 목적으로 광범위하게 사용되고 있습니다. 하지만 막상 사용하려고 하면 복잡한 설정 덕분에 쿠키가 제대로 저장/삭제되지 않거나 안전하지 않은 방식으로 사용하게 되기 일쑤입니다. 그렇기 때문에 이번 글에서는 쿠키에 사용할 수 있는 옵션에는 어떤 것들이 있으며 또 어떻게 하면 안전하게 쿠키를 사용할 수 있는 지를 알아보도록 하겠습니다.

Playground

백문이 불여일견이라는 말이 있죠?

cookie-playground로 들어가신 뒤 Getting Started에 따라서 서버와 클라이언트를 띄우면 여러가지 옵션을 가진 쿠키를 직접 생성/삭제해볼 수 있는 playground을 사용하실 수 있습니다.

한 번 직접 설치한 뒤 실행해볼까요?

Screen Shot 2019-12-08 at 10.10.49 PM.png

위와 같은 화면이 나온다면 한 번 버튼을 눌러보면서 놀아보세요.

어떤 버튼을 누르면

Screen Shot 2019-12-08 at 10.11.46 PM.png

이렇게 해당 쿠키의 내용 token=token과 함께 여러가지 쿠키 옵션에 대한 설명이 나오는 것을 알 수 있습니다.

이제 쿠키 옵션들을 하나씩 살펴봅시다.

Secure

첫 번째 옵션은 secure입니다. 이 옵션은 이름에서부터 짐작할 수 있듯이 안전한 연결(https)을 통할 때만 쿠키가 설정되는 옵션입니다. 우리가 일반적으로 사용하는 http는 암호화가 되지 않아 모든 통신 내용이 평문(clear text)으로 전달되기 때문에 보안이 취약합니다. 이때 secure 옵션을 가진 쿠키를 사용하게 되면 https 연결을 사용할 때만 쿠키가 설정되기 때문에 한결 안전하게 쿠키를 사용할 수 있습니다.

일반적으로 이 옵션은 개발환경의 경우 꺼두고, 프로덕션 환경에서만 사용하게 되는데요,

res.cookie(cookieName, cookieValue, { 
  secure: process.env.NODE_ENV === 'production'
})

와 같은 방식으로 사용할 수 있습니다.

Domain & Path

domain과 path는 모두 경로와 연관된 옵션들입니다.

이 두 가지 옵션이 필요한 것은 쿠키가 유효한 범위를 지정하기 위해서 입니다.

Screen Shot 2019-12-08 at 10.24.17 PM.png

한 번 domain = '.facebook.com'이라는 옵션이 적힌 버튼을 눌러보세요. 아마 쿠키가 설정되지 않을 겁니다. 이는 우리의 도메인이 localhost이기 때문인데요, 설정된 도메인인 .facebook.comlocalhost와 일치하지 않아서 그렇습니다.

다음은 path 옵션입니다. path는 해당 도메인의 하위 경로에 대한 범위를 지정해줍니다.

Screen Shot 2019-12-08 at 10.27.44 PM.png

이번에는 path="/app"이라고 되어 있는 버튼을 눌러봅시다.

마찬가지로 아무 일도 일어나지 않을 것입니다.

이때 localhost:3000/app으로 이동하면

Screen Shot 2019-12-08 at 10.28.49 PM.png

위와 같이 같은 이름과 값을 가진 두 개의 쿠키가 생성된 것을 알 수 있습니다.

이는 path 옵션이 유저가 현재 어떤 경로에 위치해 있느냐에 따라 접근가능한지 아닌지가 달라지기 때문입니다.

가장 기본적인 "/" 옵션은 모든 하위 경로에 대해 동작합니다. express앱에서는 아무런 옵션을 지정해주지 않을 경우 path를 "/"로 설정하며, 이 경우 해당 도메인의 어떤 경로에서도 쿠키에접근할 수 있습니다.

그러므로 아까 쿠키가 두 개 보인 것은

cookie1 path=/
cookie2 path=/app

/app 경로에 있을 경우 /, /app 모두 유효함

위와 같이 되기 때문입니다.

위 두 옵션은 쿠키를 제거할 때도 마찬가지로 적용됩니다.

/이 아닌 다른 경로로 이동한 뒤 Remove 버튼을 눌러도 쿠키가 제거되지 않는 이유는 해당 path가 설정된 쿠키만 제거할 수 있기 때문입니다.

httpOnly

secure와 헷갈릴 수 있는 이름을 가진 옵션입니다.

하지만 이 옵션은 실제로는 http 통신을 통해서만 사용될 수 있는 쿠키라는 의미로, js를 통해 제어가 가능한지를 설정합니다.

httpOnly가 설정된 쿠키의 경우 js로는 접근이 불가능하며, 그렇기 때문에 xss 공격의 가능성을 줄여줍니다.

그러나 httpOnly 옵션을 설정해두었다고 해서 모든 종류의 xss 공격으로부터 자유로운 것은 아니면 기본적으로 xss 공격을 회피하는 것은 개발자의 책임입니다.

maxAge

이름에서 알 수 있듯이 쿠키의 수명을 결정하는 옵션입니다.

통상적으로 maxAge = 0로 설정하여 쿠키를 제거합니다.
(쿠키의 경우 명시적으로 "제거"한다는 개념은 없으며 덮어쓰기를 통해 내용을 수정합니다.)

sameSite

same-site 옵션은 가장 나중에 나온 옵션으로 csrf 공격을 방어하기 위한 옵션으로 사용될 수 있습니다.

현재는 세계적으로 89% 정도의 브라우저가 이 옵션을 지원하며, 여러가지 쿠키 옵션들 중에서도 가장 강력한 보안 옵션이라고 할 수 있습니다.

이 옵션의 기본적인 아이디어는 같은 도메인에서만 쿠키를 전달하거나(Strict) 안전한 요청(get)을 통하는 경우에만 쿠키를 전달(Lax)하자는 것입니다.

이는 csrf 공격의 핵심이 로그인 사용자로 가장하여 위험한 동작을 실행시키는데 있다는 것을 생각해보면 가장 효과적인 방어책이라고 볼 수 있습니다.

클라이언트에서 사용하기

일반적인 SPA에서 가장 많이 사용되는 http client는 fetch 혹은 axios일 것입니다. 안타깝게도 이들은 기본 설정으로 서로 다른 origin 간에 쿠키를 전송하지 않습니다.

크롬의 경우 fetch의 기본 옵션은 credentials: same-origin이며 domain과 달리 origin은 포트 번호까지 포함하는 개념이기 때문에 localhost:8080localhost:3000 두 서버는 credentials 옵션을 변경하지 않고서는 cookie를 주고 받을 수 없습니다.

그렇기 때문에 현재 playground의 fetch 및 axios 설정은 각각

Axios -> withCredentials: true 
Fetch -> credentials: include

로 되어 있습니다.

마치며

쿠키는 무상태인 http 통신 과정 중에 클라이언트에 상태를 저장할 수 있는 중요한 메커니즘 중 하나입니다. 그러나 다른 클라이언트로부터 넘어오는 값과 마찬가지로 절대로 신뢰할 수 없는 값이며, 서버측의 검증이 필수입니다.

여기에 더해 적절한 보안 옵션을 같이 설정해주게 된다면 쿠키를 더욱 안전하게 사용할 수 있습니다.

긴 글 읽어주셔서 감사합니다.

profile
다양한 것들을 시도합니다
post-custom-banner

2개의 댓글

comment-user-thumbnail
2020년 4월 27일

쿠키 문제 해결하다가 여기까지 왔네요 좋은 글 잘보고 가요 감사합니당

1개의 답글