세션? 토큰? 쿠키?
인증 방식에 대해 이야기할 때 헷갈리는 개념들을 정리해보고자 합니다.
HTTP는 무상태!
우선 HTTP 프로토콜이 무상태(stateless)라는 것을 이해해야 합니다.
HTTP 프로토콜로 웹을 통해 통신해야 할 때, 우리가 보내는 각각의 요청은 독립적입니다.
요청이 끝나면 서버는 클라이언트의 정보를 잊어버리고, 요청을 보낼 때마다 클라이언트가 누군지 알려줘야 합니다.
서버에게 요청을 보낼 때 클라이언트가 누군지 알려주는 방식으로 대표적으로 세션과 토큰이 있는 것입니다.
세션 인증 방식과 토큰 인증 방식의 로그인 방법에 대해 이야기 해보겠습니다.
세션(session)
세션 인증 방식으로 로그인을 하는 과정은 다음과 같습니다.
- 클라이언트는 사용자 인증 정보(예를 들면 유저명, 비밀번호)를 담아서 서버로 로그인 요청을 보낸다.
- 서버는 사용자 정보가 일치하면 세션 DB에 세션 ID를 생성하고, 이를 클라이언트에 전송한다.
- 클라이언트는 로그인 이후 요청마다 세션 ID를 함께 전송한다.
- 서버는 세션 DB에서 세션 ID를 찾아서 요청을 처리한다.
세션은 세션 ID를 생성하고 저장하기 위한 데이터베이스(주로 Redis)가 필요합니다.
데이터베이스에 인증 정보를 저장하기 때문에 사용자 수가 늘어날수록 필요한 데이터베이스 자원도 늘어나는 단점이 있습니다.
하지만 필요에 따라 서비스에 다음과 같은 새로운 기능들을 추가할 수 있는 장점도 있습니다.
- 비정상적인 활동을 하는 경우 강제 로그아웃
- 비정상적인 위치에서 로그인 시도하거나 세션 도용이 의심되는 경우 강제 로그아웃
- 로그인 된 디바이스를 관리하고 원하지 않는 디바이스에서 강제 로그아웃
- 계정 공유 숫자 제한 (넷플릭스처럼)
토큰(token)
토큰 인증 방식으로 로그인을 하는 과정은 다음과 같습니다. JWT(JSON Web Token)을 예로 들어보겠습니다.
- 클라이언트는 사용자 인증 정보(예를 들면 유저명, 비밀번호)를 담아서 서버로 로그인 요청을 보낸다.
- 서버는 사용자 정보가 일치할 경우, 해당 정보를 사용해 서명 알고리즘을 통해 JWT를 생성한 후, 이를 클라이언트에 전송한다.
- 클라이언트는 로그인 이후 요청마다 JWT를 함께 전송한다.
- 서버는 JWT를 검증하여 유효한 토큰인 경우 요청을 처리한다.
토큰 인증 방식은 별도의 데이터베이스가 필요하지 않습니다.
토큰 자체에 사용자 정보를 담아서 서명하기 때문에, 요청이 들어올 때마다 토큰을 검증해서 그 안에 담긴 정보를 확인할 수 있습니다.
하지만 JWT 같은 토큰은 암호화된 것이 아닙니다.
누구나 페이로드에 담긴 정보를 확인할 수 있기 때문에 비밀번호와 같은 민감한 데이터는 담으면 안 됩니다.
쿠키(cookie)
그럼 쿠키는 뭘까요?
쿠키는 세션이나 토큰 같은 인증 정보를 전달하는 매개체일 뿐입니다.
만약 쿠키를 사용해 JWT를 전달한다고 한다면 로그인 과정은 다음과 같습니다.
- 브라우저가 서버로 로그인 요청을 보낸다.
- 서버는 사용자 정보를 확인하고 JWT를 생성하여 Set-Cookie 헤더에 담아서 전달한다.
- 브라우저는 로그인 이후 요청마다 Cookie 헤더에 JWT를 포함해서 전송한다.
- 서버는 JWT를 검증하여 유효한 토큰인 경우 요청을 처리한다.
이는 JWT가 아니라 세션을 사용한다고 해도 마찬가지입니다.
서버에서는 쿠키를 보낼 때 쿠키를 보낼 수 있는 도메인, 만료시간 등을 설정해서 보냅니다.
하지만 쿠키는 브라우저 스펙이기 때문에 웹 브라우저에서만 동작합니다.
iOS, Android와 같은 네이티브 앱에서는 쿠키가 없습니다.
이럴 때는 요청 헤더에 Authorization: <type> <credentials> 형식으로 보낼 수 있습니다.
HTTP 프로토콜에서는 Authorization 헤더가 사용자 인증을 위한 표준 위치이고, 토큰 기반 인증일 경우 type에 Bearer를 사용하는 것을 권장합니다. [참고1] [참고2]
✚ Cookie 와 Authorization 의 차이는?
Cookie와 Authorization 헤더는 인증정보를 전달하는 매개체입니다.
마지막으로 이 둘의 차이를 정리해보겠습니다.
Cookie
- 웹 브라우저에만 동작합니다.
- 브라우저에서 인증 정보를 자동으로 포함해서 요청을 보내게 할 수 있다는 장점이 있습니다.
- axios : { withCredentials: true } 옵션 추가
- Fetch API : { credentials: "include" } 옵션 추가
- 매 요청마다 인증 정보가 자동으로 포함되기 때문에 CSRF 공격의 위험이 있습니다.
- 👉 CSRF 토큰을 사용하거나, Referer Header 검증을 추가하여 CSRF 공격을 방어할 수 있습니다.
- Javascript 상에서 document.cookie로 접근하여 XSS 공격의 위험도 있습니다.
- 👉 서버 측에서 쿠키를 설정할 때 httpOnly 옵션을 추가하여 Javascript로 접근하지 못하게 해야 합니다.
Authorization
- 매 요청마다 인증 정보가 자동으로 포함되지 않기 때문에 CSRF 공격에도 비교적 안전합니다.
- 대신 클라이언트 측에서 인증 정보를 어디에 보관할 지 고민해야 합니다.
- 클라이언트가 웹 브라우저일 경우 MDN에서는 브라우저의 로컬스토리지나 세션스토리지를 권장합니다만, Javascript 상에서 document.localStorage, document.sessionStorage로 접근할 수 있기 때문에 여전히 XSS 공격의 위험이 있습니다.
- 👉 Access Token(이하 AT)은 메모리에 저장하고 Refresh Token(이하 RT)를 쿠키에 저장하면, XSS 공격을 피하면서도 정상적인 사용자에게는 원활한 사용자 경험을 제공할 수 있습니다. [참고]
- 로그인 요청을 통해 AT, RT를 모두 발급 받습니다.
- AT는 응답 본문으로 받아서 (JS 변수 등으로) 메모리에 저장하고, 로그인 이후 서버 요청시 Authorization 헤더에 담아서 보냅니다.
- RT는 만료 기간을 상대적으로 길게 설정한 후 httpOnly 쿠키로 받아서 저장합니다.
- AT가 만료되면 RT를 통해 새로운 AT를 요청하고 응답 본문으로 받습니다.
- RT도 만료되면 로그아웃 처리합니다.
정리
세션 기반 인증 | 토큰 기반 인증 | |
장점 | - 보안을 위한 서비스 확장이 가능함. | - 별도의 데이터베이스 자원이 필요하지 않음. |
단점 | - 세션을 저장하기 위한 데이터베이스 자원이 필요함. | - 보안을 위한 서비스 확장은 어려움. - 페이로드에 민감 데이터를 담지 않도록 주의가 필요함. |
Cookie | Authorization Header | |
특징 | - 웹 브라우저에만 동작함. - 서버에 요청시 인증정보를 자동으로 포함시킬 수 있음. |
- 네이티브 앱 클라이언트를 지원할 경우 사용 가능함. - 토큰 기반 인증일 경우 Bearer를 사용하는 것을 권장함. |
장점 | - 웹 클라이언트 개발 측에서 인증 정보 보관 방법을 따로 고려하지 않아도 됨. | - 인증 정보를 자동으로 포함하지 않으므로 CSRF 공격에 비교적 안전함. |
단점 | - CSRF, XSS 공격에 대한 방어가 필요함. | - 클라이언트 개발 측에서 인증 정보 보관 방법을 따로 고려해야 함. - 클라이언트가 웹 브라우저일 때 세션 스토리지나 로컬 스토리지를 사용할 경우 XSS 공격 위험성 있음. |
참고
https://youtu.be/tosLBcAX1vk?si=uO3RaiNXDx6ZJy8B
https://www.cloudflare.com/ko-kr/learning/access-management/token-based-authentication/
https://docs.tosspayments.com/resources/glossary/bearer-auth
https://developer.mozilla.org/ko/docs/Web/HTTP/Cookies
https://velog.io/@gwanuuoo/CSRF-%EA%B3%B5%EA%B2%A9%EA%B3%BC-%EB%B0%A9%EC%96%B4-%EA%B8%B0%EB%B2%95
https://hasura.io/blog/best-practices-of-using-jwt-with-graphql
https://medium.com/@uk960214/refresh-token-%EB%8F%84%EC%9E%85%EA%B8%B0-f12-dd79de9fb0f0