본문 바로가기
프론트엔드 개발/웹

JWT를 어디에 저장해야 할까? (feat. 쿠키)

by cozyzoey 2022. 9. 21.

JWT(Json Web Token)를 어디에 저장해야 사용자 로그인을 유지하면서도 안전할까?

 

이전에 프로젝트를 진행하면서 토큰을 로컬 스토리지에 저장하고 자동 로그인과 로그인 유지를 구현했었다. 그런데 로컬스토리지는 브라우저 콘솔에서도 window.localStorage를 입력하면 바로 접근이 가능하다. 공격자가 마음만 먹으면 인증 정보를 탈취하여 서버에 요청을 보낼수 있는 것이다. 앱에 사용자 민감 정보나 구매 등의 로직이 있다면 더 곤란하다.

그렇다고 보안을 위해서 인증 토큰을 앱 내부에서만 관리하면 자동 로그인과 로그인 유지를 포기해야 된다. 사용자 경험에 좋지 않다. 그래서 서버에서 내려준 JWT를 안전하게 관리하면서 사용자 경험도 보장할 수 있을까에 대한 고민을 하게 됐다. 브라우저를 끄고 다시 접속했을 때도 사용자 로그인을 유지하려면 토큰의 저장 위치는 localStorage 혹은 쿠키를 생각해볼 수 있다. sessionStorage는 브라우저를 끄면 데이터가 지워지기 때문에 적합하지 않다. 어떤 식으로 공격하는지 먼저 알아보도록 하자.

JWT를 관리하기 위한 지도

 

XSS 공격

Cross-site Scripting의 약자다. 공격자가 웹사이트를 조작하여 악의적인 자바스크립트 코드를 피해자 브라우저에서 실행시키는 것이다. 웹사이트 내의 동작과 데이터를 제어할 수 있게 된다.

악성 스크립트의 출처에 따라 크게 3가지 종류로 나눌수 있다. (XSS 공격의 종류)

종류 Reflected XSS Stored XSS DOM-based XSS
악성 스크립트의 출처 현재의 HTTP 요청 웹사이트의 데이터베이스 클라이언트측 코드

만약 웹사이트가 URL에서 데이터를 가져와 사용한다면 공격자가 작성한 URL을 방문시 아래처럼 악성 스크립트가 주입되어 실행된다. 이를 Reflected XSS라고 한다.

https://insecure-website.com/status?message=<script>/*+악성+코드+*/</script>
<p>Status: <script>/* 악성 코드 */</script></p>

localStorage에 저장된 JWT는 XSS 공격에 무방비하다. 쿠키의 경우 httpOnly 설정을 통해 XSS 공격을 완화할 수 있다. 쿠키 정보는 document.cookie를 사용하면 브라우저에서 접근 가능한데 httpOnly 설정을 하면 클라이언트측에서 쿠키에 접근할 수 없도록 한다. 즉, document.cookie를 통해 쿠키를 읽을수도 조작할수도 없게 된다. localStorage나 sessionStorage에 저장된 정보는 httpOnly 옵션이 없는 쿠키와 마찬가지라고 할 수 있다.

A cookie with the HttpOnly attribute is inaccessible to the JavaScript Document.cookie API; it's only sent to the server. For example, cookies that persist in server-side sessions don't need to be available to JavaScript and should have the HttpOnly attribute. This precaution helps mitigate cross-site scripting (XSS) attacks.

출처: MDN - Using HTTP cookies

 

CSRF 공격

Cross-site Request Forgery의 약자다. 사용자가 의도하지 않은 작업을 수행하도록 유도하는 것이다. 사용자 계정의 이메일이나 비밀번호를 바꾸도록 할 수 있다. 그러면 공격자가 사용자의 계정을 장악하게 되는 것이다. 

아래와 같은 악성 HTML을 악성 웹사이트에 위치시키고 피해자가 해당 웹사이트에 방문하도록 유도한다. 피해자는 이메일이나 SNS 메시지를 통해서 접속 링크를 받을 수 있다. 피해자가 악성 웹사이트에 방문하면 자동으로 폼이 제출되어 타겟 웹사이트로 HTTP 요청을 보내게 된다. 사용자가 타겟 웹사이트에 쿠키 기반으로 로그인된 상태라면 쿠키와 함께 HTTP 요청이 전송될 것이고 정상 처리될 것이다.

<html>
    <body>
        <form action="https://vulnerable-website.com/email/change" method="POST">
            <input type="hidden" name="email" value="pwned@evil-user.net" />
        </form>
        <script>
            document.forms[0].submit();
        </script>
    </body>
</html>

쿠키에 samesite=strict 설정을 하면 CSRF 공격을 방지할 수 있다. 사용자가 외부에서 요청을 보낼때 samesite 옵션이 잇는 쿠키는 전송되지 않는다. samesite=lax는 strict보다 조금 더 완화된 버전이다.

 

쿠키 적용 예시

XSS 공격을 완화하는 httpOnly 옵션, CSRF 공격을 방지하는 samesite 옵션을 적용하여 실제 쿠키를 설정해보자. Next.js로 개발한 웹사이트에서 JWT를 localStorage 대신에 쿠키에 저장하도록 했다. Next.js의 API Routes를 활용하여 사용자 회원가입, 로그인시에 HTTP 응답 헤더에 쿠키를 설정해주었다.

Next.js 쿠키 설정

res.setHeader(
  "Set-Cookie",
  cookie.serialize("token", jwt, {
    httpOnly: true, // 클라이언트에서 쿠키 접근 차단
    secure: process.env.NODE_ENV !== "development", //true인 경우 HTTPS 통신할때만 쿠키 전송
  	maxAge: 60 * 60 * 24 * 30, // 30일간 유지
  	sameSite: "strict", // 사이트 외부에서 요청을 보내면 쿠키 전송하지 않음
  	path: "/", // 쿠키에 접근할 수 있는 경로 설정, 루트로 설정해서 사이트 모든 페이지에서 쿠키에 접근할 수 있음
  })
);

 

참조

https://javascript.info/cookie

https://portswigger.net/web-security/cross-site-scripting

https://portswigger.net/web-security/csrf

https://tkacz.pro/how-to-securely-store-jwt-tokens/

댓글