웹 인증의 모든 것: 쿠키, 세션, JWT, 그리고 보안 아키텍처

Category
Frontend
Tags
Published
February 11, 2026
Last updated
Last updated February 13, 2026
웹 개발을 하다 보면 인증(Authentication)에서 막히는 순간이 온다. "로컬에서는 되는데 배포하면 안 돼요", "쿠키가 안 구워져요", "CORS 에러가 떠요". 이 글은 Better Auth, Cloudflare Workers, Next.js 환경을 포함하여, 웹 인증의 밑바닥 원리부터 실무 아키텍처까지 모든 정보를 총정리한다.

1. 쿠키 vs JWT: 무엇을 선택해야 할까?

인증 시스템(Better Auth 등)을 구성할 때 가장 먼저 마주하는 선택지다.

✅ 쿠키(Cookie) 기반 세션 (추천)

브라우저 환경에서는 이 방식이 **표준(Standard)**이다.
  • 동작: 로그인 성공 시 서버가 Set-Cookie 헤더를 보냄 → 브라우저는 이후 모든 요청에 자동으로 쿠키를 붙여 보냄.
  • 서버 상태: 세션 ID가 DB(D1, Redis 등)에 저장됨.
  • 장점:
    • 보안: HttpOnlySecure 옵션으로 XSS 방어 가능.
    • 제어: 서버에서 세션을 삭제하면 즉시 로그아웃(Revoke) 가능.
  • Better Auth 기본값: Workers + D1 조합 시 이 방식이 기본이다.

✅ JWT (JSON Web Token)

  • 동작: Authorization: Bearer <token> 헤더를 클라이언트가 직접 붙여서 보냄.
  • 장점: Stateless(DB 조회 없음). 모바일 앱이나 서버 간 통신(S2S)에 적합.
  • 단점: 한 번 발급된 토큰은 만료 전까지 취소하기 어려움(Blacklist 관리 필요). 토큰 저장 위치에 따른 보안 취약점 존재.

2. 쿠키의 해부: "어떤 인증은 되고, 어떤 건 안 되는 이유"

"쿠키가 안 붙어요"라는 말은 쿠키의 주소(Scope) 설정이 틀렸다는 뜻이다. 서버가 Set-Cookie를 보낼 때 결정된다.

핵심 속성 3가지

  1. Domain (어디서 쓸 것인가?)
      • 지정 안 함 (기본값): Host-Only Cookie. 쿠키를 구운 그 도메인(api.myapp.com)에서만 사용 가능. 서브도메인 공유 불가. 가장 안전.
      • 지정함 (Domain=myapp.com): myapp.com 및 모든 서브도메인(api.myapp.comwww.myapp.com) 공유 가능.
      • 주의: Domain=.myapp.com의 앞 점(.)은 최신 브라우저에서 무시된다. myapp.com과 동일.
      • 상위 전송 불가: api.myapp.com에서 구운 쿠키는 상위인 myapp.com으로 절대 전송되지 않음. 공유하려면 상위 도메인(myapp.com)으로 설정해야 함.
  1. Path (경로)
      • 보통 Path=/로 설정하여 모든 경로에서 사용.
  1. SameSite (누가 보낼 수 있는가?) - 가장 중요
      • CSRF(사이트 간 요청 위조) 방어를 위한 속성.
속성
동작 방식
설명
Lax (기본)
Same-Site + Top Level Navigation(GET) 허용
일반적인 웹사이트 표준. 링크 클릭 이동은 허용, fetch/iframe 등은 차단.
Strict
Same-Site Only
외부에서 들어오는 모든 요청(링크 클릭 포함)에 쿠키 차단. UX 안 좋음.
None
Cross-Site 허용
모든 곳에서 전송 가능. 단, Secure(HTTPS) 필수.
Sheets로 내보내기

3. SameSite와 보안 시나리오 (Deep Dive)

"SameSite=Lax면 안전한가요?", "GET 요청은 다 되나요?"에 대한 명확한 답.

Q. Lax에서 fetch(GET)은 되나?

아니오. (Cross-Site일 경우)
  • 시나리오: evil.com 접속 → JS로 fetch("https://my-community.com/api/me") 실행.
  • 결과: 쿠키 전송 안 됨. 로그인 풀린 상태로 응답 옴.
  • 예외: 사용자가 <a> 태그를 눌러서 페이지를 **이동(Navigate)**하는 경우(Top-level Navigation)에는 쿠키가 붙음.

Q. evil.com에서 내 서버로 요청을 100번 날리면 조회수가 오르나?

  • Lax일 때: 쿠키가 안 붙으므로 "비로그인 조회수"라면 오를 수 있음.
  • None일 때: 쿠키가 붙어서 날아감. "로그인 유저 조회수"가 오를 수 있음.
  • CORS와의 관계: CORS 때문에 evil.com이 응답(결과)을 읽지는 못함. 하지만 서버 내부에서 **로직(조회수 증가, 좋아요, 송금)**은 실행될 수 있음. 이것이 CSRF 공격.

Q. Public Suffix List (PSL)란?

  • a.vercel.app과 b.vercel.app은 같은 vercel.app인데 쿠키 공유가 될까?
  • 정답: 절대 안 됨.
  • 브라우저는 **PSL(Public Suffix List)**에 등록된 도메인(comco.krvercel.appgithub.io 등)을 기준으로 도메인을 격리함.
  • 따라서 vercel.app은 마치 .com처럼 취급되어, 사용자 간 쿠키 공유가 원천 차단됨.

4. 도메인 아키텍처별 전략

내 서비스의 도메인 구조에 따라 인증 전략이 완전히 달라진다.

Case 1: 단일 도메인 / 서브도메인 (Best Practice)

  • 구조: myapp.com (프론트) + api.myapp.com (백엔드)
  • 설정:
    • Cookie: Domain=.myapp.comSameSite=LaxHttpOnlySecure
  • 특징: 쿠키 공유 가능, 보안 좋음, CSRF 위험 낮음.

Case 2: 완전 분리 도메인 (Difficult)

  • 구조: frontend.com + api.backend.com
  • 설정:
    • Cookie: SameSite=NoneSecure
  • 특징: 쿠키 공유 불가능(Domain 불일치). None 사용으로 인해 CSRF 취약. 추가 보안 대책(CSRF Token) 필수.
  • 주의: frontend.com에서 쿠키를 구우면 backend.com으로는 절대 전송되지 않음.

Case 3: BFF (Backend For Frontend) 패턴 (Next.js 추천)

  • 구조: 브라우저 ↔ frontend.com (Next.js 서버) ↔ api.backend.com
  • 설정:
    • 브라우저는 frontend.com 쿠키만 가짐 (SameSite=LaxHttpOnly).
    • Next.js 서버가 API 호출 시 토큰을 헤더에 붙여서 중계.
  • 특징: 브라우저 보안 이슈(CORS, Cross-Site Cookie)를 서버 사이드에서 모두 해결. 가장 추천하는 구조.

5. 저장소 보안: Cookie vs LocalStorage vs Memory

"토큰을 어디에 저장해야 안전한가?"
저장소
XSS 취약점
CSRF 취약점
특징
HttpOnly 쿠키
안전 (JS 접근 불가)
취약 (자동 전송됨)
SameSite 설정과 CSRF 방어 필수. 가장 권장됨.
일반 쿠키
취약 (JS 접근 가능)
취약
LocalStorage와 다를 바 없음. 쓰지 말 것.
LocalStorage
매우 취약 (영구 탈취)
안전 (자동 전송 안 됨)
XSS 한 번 터지면 토큰 영구 탈취.
메모리 (JS 변수)
비교적 안전 (새로고침 시 증발)
안전
가장 안전하나, 새로고침 시 로그인이 풀림.
Sheets로 내보내기

🏆 실무 추천 조합 (Hybrid)

  1. Refresh Token: HttpOnly Cookie에 저장 (브라우저 닫아도 유지, XSS 방어).
  1. Access Token: 메모리(JS 변수)에 저장.
  1. 동작: 앱 시작 시(또는 새로고침 시) Refresh Token으로 Access Token을 재발급받아 메모리에 적재.

6. 결론 및 체크리스트

  1. 기본은 쿠키다: 웹 앱이라면 HttpOnly Cookie 세션 방식을 써라. JWT + LocalStorage는 보안 구멍이다.
  1. SameSite=Lax가 기본이다: GET 요청으로 상태를 변경(DB 수정)하게 만들지 마라. Lax는 GET 이동을 허용한다.
  1. 도메인이 다르면 BFF를 고려하라: frontend.com과 backend.com이 다르다면 Next.js 서버를 프록시로 쓰는 BFF 패턴이 정신 건강과 보안에 이롭다.
  1. CORS와 CSRF는 다르다: CORS 설정을 잘했다고 CSRF가 막히는 게 아니다. SameSite=None을 쓴다면 반드시 CSRF 토큰이나 Origin 검증을 추가하라.
  1. PSL을 기억하라: a.vercel.app과 b.vercel.app은 남남이다. 쿠키 공유하려 하지 마라.
 

Reference