[개념정리] JWT 구현 방법 - 프론트

flaxinger·2022년 1월 13일
3

개념정리

목록 보기
3/3

해당 글은 JWT 및 인증 전반에 대한 기본적인 내용을 다루지 않습니다. 이에 대한 설명은 기존 글을 참고 부탁드립니다. 더불어 필자는 백엔드를 지망하는 학생으로, React 및 프론트 개발에 대한 지식이 얕으니 부정확한 정보가 있다면 지적을 부탁드립니다.

목차

  1. JWT를 어떻게 저장할까?
    1.1. 토큰을 메모리에 저장한다면?
    1.1. 토큰을 브라우저 로컬 스토리지에 저장한다면?
    1.3. 토큰을 브라우저 쿠키로 (직접)저장한다면?
    1.4. 토큰을 브라우저 쿠키로 (자동)저장한다면?
  2. 결론 및 CSRF의 소개

JWT와 보안

JWT로 인증을 할 때 필수적인 두개의 옵션이 있다. HTTPONLY와 SECURE. 근데 여기서 아리송한 부분이 있다. HTTPONLY 옵션은 자바스크립트가 토큰을 읽지 못하도록 하는 옵션인데, 도데체 자바 스크립트가 토큰을 읽지를 못하면 어떻게 요청을 하라는 말일까? 해당 글에서는 이와 관련한 다양한 전략과 보안 상의 장단점을 다루고자 한다.

1. JWT를 어떻게 저장할까?

JWT에 대해 조사를 하다 보면 간혹 이런 글을 볼 때가 있다. 다음은 Stack overflow에서 서버의 토큰 발급 방법에 대한 답변이다 출처.

there is no standard for how to return JWT token to the client...

실제 전달 방법에 표준이 있는지 없는지 여부는 확인하지 못했으나, 분명한 것은 표준이 없다고 하더라도 모든 전달 방식이 용인되는 것은 아니라는 점이다. 이 때 대부분 경우 토큰의 저장 방식이 중요한데 이와 관련하여 알아보자. 더불어 서버에서 토큰을 전달하는 방식 또한 다룰텐데, 제목 옆에 BODY, HEADER로 표기하고 이후 추가 설명을 하겠다.

결론부터 말하자면 여기서 다루는 방법 중 JWT를 쿠키로 저장하고 클라이언트 스크립트 또한 읽지 못하도록 하는 것이 가장 좋다.

1.1. 토큰을 메모리에 저장한다면?(BODY/HEADER)

전달 방식: 서버에서 바디/헤더에 토큰을 담아 전달(httponly 없음)

일단 쉬운 것부터 짚고 넘어가자. 토큰을 변수로 메모리에 저장한다면 무슨 문제가 있을까? 아무리 SPA라고 하더라도 리프레쉬를 하면 모든 React State 변수는 디폴트 값으로 초기화 된다. 따라서 이는 편리성 차원에서 이미 탈락이다. 리프레쉬할 때마다 로그인을 할 수는 없지 않은가.

1.2. 토큰을 브라우저 로컬 스토리지(혹은 세션 스토리지)에 저장한다면?(BODY/HEADER)

전달 방식: 서버에서 바디/헤더에 토큰을 담아 전달(httponly 없음)

메모리에 저장하는 방식과 달리 초보가 할 수 있는 가장 쉬고 현실적인 실수라고 생각한다. 하지만 이런 방식은 공격에 취약하다. 필자가 잘 몰라서 깊게 다루지는 않겠으나, 대표적으로 XSS(Cross-Site Scripting)라는 script injection 공격에 당하기 쉽다. 누가 쿠키 따위를 훔치겠냐라는 생각이 들 수 있지만.. 누가 아는가.. 배가 많이 고팠나보지.. 이는 로컬 스토리지와 세션 스토리지는 도메인만 동일하다면 주입된 스크립트가 스토리지의 모든 정보를 조회, 이용할 수 있기 때문이다. 따라서 이 방법은 무조건 피해야한다.

1.3. 토큰을 브라우저의 쿠키로 (직접)저장한다면?(BODY/HEADER)

전달 방식: 서버에서 바디/헤더에 토큰을 담아 전달(httponly 없음)

이 방식은 쿠키를 브라우저에 저장한다. 쿠키를 response에서 받아서 자바스크립트로 저장하는 기법이다. 이 때 메모리에 저장하는 것과 달리 다른 창에서 접속하거나 리프레쉬를 하더라도 토큰이 날아가지는 않는다.

아쉽게도 이러한 방식 또한 XSS에 취약하다. 그 이유는 기존 글에서도 잠깐 다루었지만 브라우저의 쿠키는 HTTPONLY 설정이 되어 있지 않다면 자바스크립트로 접근할 수 있기에 document.cookie 명령으로 콘솔에서 조회가 가능하다.

다만 인증이 아닌 사용자를 트래킹하는 쿠키는 이런 방식으로 저장되어도 아무 문제가 없다. 예로 지금 크롬 개발자 툴을 켜서 document.cookie를 실행하면 _ga의 키로 식별되는 쿠키를 발견할 수 있는데(없다면 심히 유감입니다), 이는 Google Analytics의 쿠키이다.

1.4. 토큰을 브라우저의 쿠키로 (자동)저장한다면?(HEADER)

전달 방식: 서버에서 쿠키를 헤더(Set-Cookie)에 넣고 HTTPONLY, SECURE를 설정해서 전달한다.

자 이제 본론이다. 여기서 필자를 포함하여 많은 분들이 물음표를 던진다. HTTPONLY를 설정하면 클라이언트를 포함한 모든 Javascript가 토큰을 읽지 못하는데 도데체 어떻게 향후 요청에 이를 포함하란 말인가!? 이 부분에 대해서는 걱정할 필요가 없다. 이는 브라우저가 모두 알아서 해주기 때문이다. React/Axios를 사용한다면 다음과 같이 withCredentials 설정을 하면 자동으로 JWT를 헤더에 넣어 요청한다고 한다. 단 서버에서 JWT를 발급 받을 때 헤더의 Set-Cookie를 통해 받아야 가능하다고 한다.

방법 1: 요청 시 JWT 추가

axios.get(API_SERVER + '/todos', { withCredentials: true });

방법 2: 모든 Axios 요청에 JWT 추가하도록 설정

axios.defaults.withCredentials = true

방법 3: 일부 인스턴스에 JWT 추가

const instance = axios.create({
   withCredentials: true,
   baseURL: API_SERVER
})
instance.get('/todos')

이렇게 설정을 해주면, 비로소 XSS 공격에서 자유로워질 수 있다. 따라서 기존의 방법보다 월등히 안전한 처리 방식이다.

2. 결론 및 CSRF의 소개

이번 글은 HTTPONLY를 통한 JWT 관리의 중요성과 실제 구현 방법을 간략하게 다루었다. 필자의 인증에 대한 지식이 정말 얕지만 비슷한 초보자 분들께 학습 팁을 드리자면 온라인상에서 발견할 수 있는 인증 예제는 대부분 학습에 유용하도록 되어 있다는 것이다. 즉 대부분 경우 토큰, 쿠키, 세션을 눈으로 직접 보는게 학습에 도움이 되니 HTTP 응답의 바디에 넣거나 적어도 HTTPONLY를 적용하지 않고 노출시킨다는 것이다. 하지만 현업에서 이러한 예제와 동일하게 인증 시스템을 설계하면 큰일이 날 수 있으니 꼭 유의하자.

그렇다면 이게 끝일까? 아쉽게도 그렇지 않다. 이렇듯 JavaScript도 접근하지 못하는 쿠키는 CSRF 공격에는 취약하다. CSRF란 Cross-Site Request Forgery의 약자로, 브라우저 디폴트 설정이 cross domain 요청에 모든 쿠키를 넣어서 보낸다는 점을 악용한 공격이다. 흔히 광고나 링크의 형태로 사용자의 클릭을 유도하는데, 클릭을 하면 브라우저에 저장된 모든 쿠키가 고스란히 노출되게 된다. 이 점은 기회가 된다면 향후 글에서 다루도록 하겠다.

출처

How to Secure JWT in a Single Page Application
How to securely store JWT tokens
Stack Overflow: Make Axios send cookies in its requests automatically
CSRF Attack Examples and Mitigations

profile
부족해도 부지런히

0개의 댓글