Frontend
인증 토큰을 어디에 저장해야할까?
웹 개발에서 인증(Authentication)을 어떻게 구현할 것인지, 그리고 프론트엔드에서 토큰 기반 인증 방식에서 토큰을 관리하는 방법을 알아보고자 합니다.
인증 방식에 대해
기본적으로 사용자를 식별하기 위해서는 서버에 데이터를 담아 보내야 하는데, HTTP 프로토콜을 사용한다. 문제는 HTTP에는 상태가 없다 는 특징이 있다.
- 상태가 없기 때문에 각 클라이언트를 식별할 수 없다.
- 각각의 HTTP 요청이 독립적이고 서버는 이전 요청에 대한 정보를 알 수 없다.
- 즉, 사용자의 상태(로그인, 로그아웃 등)를 추적하고 권한을 부여할 수가 없다.
HTTP 프로토콜 만으로는 클라이언트를 식별할 수가 없다. 로그인 페이지를 만들더라도 새로운 요청을 할 때 또 로그인을 해야 된다.
웹 개발에서 사용자 인증과 상태 관리를 위해서 서버에 세션을 사용하거나, 토큰을 사용하는 등의 방식으로 클라이언트와 서버 간 상태 정보를 공유한다.
모던한 인증 방식은 크게 두 가지. 세션 기반 인증, 토큰 기반 인증이 있고 이외에도 Access Token + Refresh Token 방식, OAuth2.0(Open Authentication) 방식, SNS 로그인 방식 등 여러가지 방식이 있다.
1. 쿠키 인증 방식
서버 DB에 사용자의 계정과 비밀번호를 담겨 있고, 사용자가 입력한 아이디와 비밀번호를 서버에 보내 적합한 정보인지 확인 하여 인증하는 방식이다. 서버는 사용자 인증 정보를 클라이언트의 쿠키에 저장한다.
이후 사용자가 권한이 필요한 요청을 보낼 때, 쿠키를 함께 보내 서버에서 인가 처리를 하는 방식이다. 주로 HTTP 헤더에 인증 정보를 담아 보내는데, 보안에 취약하므로 HTTP Secure(https)와 함께 사용한다. 쿠키는 데이터 크기가 제한적이며, 위조가 가능하기 때문에 보안에 매우 취약하다. 이런 문제들 때문에 요즘 잘 안 쓴다.
2. 세션 기반 인증(Session-Based Authentication)
서버가 사용자의 인증 정보를 관리하는 방식인 건데 그 방법으로 세션(Session)을 사용한다는 거다. 사용자를 식별하고 인증하기 위해 세션 ID를 사용하는데, 사용자가 로그인할 때 서버는 세션 ID를 생성하고 이를 클라이언트 사이드에 저장한다. (쿠키나 스토리지를 사용) 인증된 사용자의 모든 요청에 세션 ID를 포함하여 사용자를 식별하는 방식이다.
- 세션은 서버에 있고, 사용자가 식별될 때 세션 ID를 생성하여 이를 클라이언트에게 전달한다.
- 사용자의 상태(세션 데이터)는 서버에 저장된다. 세션은 서버 메모리나 데이터베이스, 파일 시스템 등에 저장하며 이는 환경에 따라 얼마든지 달라질 수 있다. (보통 DB를 사용한다.)
- 보안 상 안전하며 하나의 계정 정보를 관리하거나 사용자의 디바이스 별 인증을 관리하거나(여러 디바이스에서 접속을 하지 못하게 한다거나), 비정상적인 접근을 판단하여 강제로 로그아웃 시키는 등의 여러 기능들을 구현할 수 있다.
- 세션이 무거워질 수록 서버 부하가 생기고, 관리할 것들이 많다.
3. 토큰 기반 인증(Token-Based Authentication)
인증 정보를 토큰 형태로 클라이언트에게 제공하고, 클라이언트는 이 토큰을 사용해서 서버에 접근하는 방식이다. 토큰은 서버에서 비밀 키를 사용하여 생성한다. 일반적으로 JWT(JSON Web Token)을 사용하여 토큰을 생성하고 검증한다. 서버 측에서 세션 정보를 유지할 필요가 없고 효율적이다. 토큰에는 조건 또는 유효 기간 후에 만료될 수 있다.
- 쿠키 인증 방식과 비슷하지만 쿠키를 직접 보내는 것이 아니라 토큰을 매개체로 한다는 부분이 다르다.
- 클라이언트에서는 요청에 따른 인가에 토큰을 포함하여 요청하며, 서버에서는 토큰 검증 후 사용자를 인증하거나 인가한다.
- 토큰 검증이란, 토큰이 포함된 요청을 받을 때 서버는 자신의 비밀 키로 만들어진 토큰인지를 확인한다. 위조 혹은 변조된 토큰을 구분할 수 있게 된다.
- 쿠키 방식보다 보안이 뛰어나고 서버 부하가 덜하지만, 세션 방식의 여러 기능들을 사용할 수 없다. 물론 보안도 세션 방식보다는 떨어진다.
토큰도 상태가 없는 특징이 있고, 서버에는 토큰에 대한 정보를 가지고 있지 않다. 토큰 자체의 만료 정보도 토큰이 가지고 있다. 이미 생성되어 제공한 토큰에 대해서 서버는 아무런 제어권을 가지지 못한다. 현재 많은 서비스들은 Access Token + Refresh Token을 함께 사용하는 방식을 사용하는데, Access Token으로 사용자의 요청을 매우 짧은 시간 동안 유지하고 이 토큰이 만료되면 함께 제공한 Refresh Token을 서버에 보내 Access Token을 재발급 받는 방식이다. 물론 이 방식도 Refresh Token까지 노출되면 문제가 생긴다.
최근 진행한 SNS 개발 프로젝트에서 백엔드 API를 강사님이 다 제공해주셨는데, 토큰 기반 인증을 사용했다. HTTP 요청에 토큰을 포함하는 방식인데 제공받은 토큰을 어떻게 관리해야 좋을 지에 대한 궁금증이 생겼다.
토큰을 어디에 저장해야 할까?
최근 진행한 SNS 프로젝트에서 제공하는 API에는 JWT를 제공했다. 가장 쉬운 방법으로 로컬 스토리지에 저장하여 사용하게 했는데, 이는 보안 상 문제가 있을 수 있었다.
토큰을 저장하는 위치에 따라 보안 위험의 종류가 다르다. 주로 XSS(Cross Site Scripting), CSRF(Cross Site Request Forgery) 등의 공격을 한다.
로컬 스토리지와 세션 스토리지에 저장할 때
장점
👍 자바스크립트로 손쉽게 관리가 가능하다.
👍 일반적인 공격에 대한 보안이 가능하다.
👍 로컬 스토리지의 경우 데이터 영구 저장이 가능하다.단점
👎 자바스크립트로 접근이 가능하다. 즉, XSS 공격에 노출된다.
👎 브라우저 탭 간 데이터 공유가 어렵다.쿠키에 저장할 때
장점
👍 자바스크립트로 접근할 수 없어서(httpOnly) XSS 공격에 취약하지 않다.
👍 자동으로 http 요청에 포함되어 보내진다.
👍 httpOnly
나 secure
옵션을 사용하여 보안을 높일 수 있다.단점
👎 크기 제한이 있다. (약 4KB)
👎 CSRF 공격에 노출된다.CSRF(Cross-Site Request Forgery)
한국말로
사이트 간 요청 위조
다. 공격자가 특정 웹페이지를 보안에 취약하게 하거나 수정 및 삭제 등의 작업을 하게 만드는 공격 방법이다. 공격 난이도가 높지 않다.피싱 사이트를 만들어서 사용자를 로그인시켜 위조 요청을 보내거나, 게시판에 악성 스크립트를 심어 다른 사용자들이 클릭하면 해당 사용자의 쿠키로 인증하여 공격하는 방식 등이 있다.
CSRF 예방 방법들
- Referer 요청 헤더 검증하기
- Anti-CSRF 토큰
- Double Submit 쿠키
- SameSite 쿠키
- 패스워드 확인
- Captcha 검증 사용
멘토링 과정에서 토큰을 어디에 저장해야 하는 지에 대한 질문을 했을 때, 정답을 주시진 않았지만 힌트를 받았다. 사실 정답에 가깝다. 쿠키에 저장하라는 것. 그리고 그 이유는 내가 찾아보아야 했다.
스토리지보다 쿠키가 선호되는 이유
로컬 스토리지나 쿠키 둘 다 보안 상 문제가 있다. 그런데 왜 스토리지보다 쿠키를 선호하는 걸까?
아래와 같은 장점들 때문이다.
- 쿠키에
httpOnly
옵션을 사용하면 공격자가 접근하기가 조금 더 까다롭도록 할 수 있다.
sameSite
옵션,Secure
옵션 그리고 쿠키의유효 범위를 설정
하는 등의 여러 옵션들로 공격을 어느정도 예방이 가능하기 때문이다.
옵션명 | 내용 |
httpOnly | 자바스크립트, 즉 브라우저에서 쿠키에 접근할 수 없게 한다. |
Secure | HTTPS 연결에서만 쿠키가 전송되도록 한다. |
SameSite | 쿠키가 어떤 상황에서 전송되어야 하는 지 제어할 수 있다.
Strict : 동일한 사이트에서만 전송
Lax : 일부 상황에서는 다른 사이트로의 요청에도 쿠키를 전송
None : 쿠키를 모든 사이트에 전송 |
그리고 여러 사이트를 보며 느낀 점
XSS 공격 방지는 보안의 아주 기초적인… 아주~ 근본적인… 아주 뿌리깊은 기초?와 같은 것처럼 취급된다. 즉, 아주 기본이라는 것. CSRF 공격 방지도 중요하지만 XSS는 필히 막아야 하는 것처럼 취급되고 있더라.
Access Token + Refresh Token을 사용할 경우에는?
[참고자료의 원문과 번역본]
이 때는 세 가지 옵션이 있을 수 있다.
- Access Token을 스토리지에 저장하고, Refresh Token을 스토리지 혹은 httpOnly 쿠키에 저장하는 방법
- XSS 공격에 취약해진다.
- Acess Token과 Refresh Token을 httpOnly 쿠키에 저장하는 방법
- CSRF 공격에는 취약하지만 어느정도 예방이 가능하고, XSS도 어느정도 예방할 수 있다.
- Refresh Token을 httpOnly 쿠키에 저장하는 방법
- CSRF 공격으로부터 안전하고, XSS도 어느정도 예방할 수 있다.
- 그럼 Access Token은? ⇒ 메모리에 저장하기도 한다. 메모리에 저장하면 탭을 전환하거나 페이지를 새로고침 할 때 사라진다.
토큰을 쿠키에 저장하고 요청 시 전송하기
코드가 보이지 않으면 새로고침을 해주세요!
프론트엔드 자바스크립트에서 Access Token을 쿠키에 저장하고 http 요청을 보내는 방법들이다.
쿠키에 토큰 저장하기
// 토큰을 받아온 후 쿠키에 저장 document.cookie = `authToken=${token}; Secure; HttpOnly; SameSite=Strict; path=/`;
- Secure, HttpOnly, SameSite(Strict) 옵션을 적용
HTTP 요청 시 토큰 전송하기
fetch('/api/data', { method: 'GET', headers: { 'Authorization': 'Bearer ' + token, // 토큰을 헤더에 포함 }, }) .then(response => response.json()) .then(data => { console.log(data); }) .catch(error => { console.error('Error:', error); });
Authorization
헤더에 Bearer 스키마와 함께 전송
완벽한 방법은 없다.
뭐… 대학 시절부터 귀에 딱지가 않도록 들은 얘기다.
“모든 기술에는 장-단점, 작용-반작용, 트레이드 오프(trade-off)가 있다.”
완벽한 기술이란 건 없으니까 상충 관계와 프로젝트의 환경에 따라 기술의 적용을 달리해야 한다. 지도 교수님께서 어떤 개념이나 기술을 하나 설명하실 때마다 장점과 단점을 외우게 하셨고, 그리고 항상 저 말을 해주셨다.
애초에 HTTP에 상태가 없기 때문에 이런 보완 방식들이 나온 것이니, 뭔가 뻥~! 속 시원한 방법이 나와 있지 않더라. 사실 이 내용도 몇 년 된 내용들을 책이나 인터넷에서 학습하고 작성하는 것이기 때문에 실제 현업에서 정확하게 어떻게 적용되고 있는지 알기가 어렵다.
그리고 요즘은 OAuth2.0을 사용해서 대규모 서비스의 사용자 정보를 제공 받아서 내 서비스에 활용하는 방식이 많다. OAuth가 적용되는 예제 튜토리얼 등을 진행해보면서 간단히 찍먹만 해봤었는데, 어렵다. 되긴 되는데 어떻게 해서, 왜 되는지 정확히 잘 모르겠다. 특히 글로 이해하려고 읽을 수록 더 어려워졌던 것 같다.
모든 인증 방법을 다 아는 것이 물론 좋지만, 중요한 건 장점과 단점을 빠르게 파악하고 내 프로젝트 환경에서는 어떤 방식을 적용해야 할 것인지 결정하는 게 더 중요하다. 공부할게 지지리도 많다. 매번 투덜대게 되지만 필요하면 배워서 할 수 밖에.