JWT vs 세션, 그리고 JWK의 정체
TL;DR
"JWT가 죽은 게 아니라, JWT를 브라우저 세션 관리 도구로 쓰던 오용이 교정된 것."
다시 상기하자, 쿠키 vs JWT는 대립 축이 아님, 쿠키는 전송 수단, JWT는 토큰 포맷.
발단
better-auth, Supabase Auth 등 최근 인증 솔루션들이 JWT(JWK)를 지원하면서도
기본값은 쿠키+세션인 걸 보고 의문이 생겼다. 분명 몇 년 전엔 "JWT로 통일" 분위기였는데?
1. 인증 트렌드는 실제로 한 바퀴 돌았다
JWT 전성기 (~2015–2021)
SPA + REST API 분리 아키텍처의 유행과 정확히 겹친다.
프론트와 백이 다른 도메인, "stateless라 스케일링에 좋다"는 마케팅,
튜토리얼마다 복붙되던 localStorage 저장 패턴.
실무에서 누적된 문제
| 문제 | 내용 |
|---|---|
| 폐기(revocation) | 로그아웃·강제 탈퇴·권한 변경을 즉시 반영하려면 결국 서버에 블랙리스트/세션 테이블 필요 → "stateless" 붕괴 |
| refresh token | access를 짧게 + refresh를 DB에 저장·검증 → 사실상 세션의 재발명. *"You reinvented sessions with extra steps"* |
| localStorage | XSS에 그대로 노출. httpOnly 쿠키를 포기할 이유가 없음 |
회귀의 결정타 (2022–2025)
Next.js App Router/RSC, Remix 등 서버 렌더링 회귀로 프론트와 백이 같은 first-party 서버가 됨.
이 구조에서는 httpOnly 쿠키가 자연스럽고(CSRF는 SameSite로 대부분 해결),
클라이언트가 토큰을 직접 다룰 이유가 사라짐. Lucia 작자의 세션 기반 주장,
better-auth의 쿠키+DB 세션 기본값이 이 맥락.
쿠키는 전송 수단, JWT는 토큰 포맷.
예로 Supabase는 내부적으로 여전히 JWT 기반(access token이 JWT)이지만,
저장/전송은 httpOnly 쿠키로 하는 하이브리드다.
현재 수렴된 컨센서스
- first-party 웹앱 세션 → httpOnly 쿠키 (내용물은 opaque 세션 ID든 짧은 수명 서명 토큰이든)
- 서비스 간 통신 / 서드파티 API / OIDC 연합 인증 → JWT가 여전히 표준, 대체재 없음
- 하이브리드 → 짧은 수명(수분~1시간) JWT access + DB 추적 refresh/세션. 검증은 stateless의 이점, 폐기는 refresh 시점에 가능
2. JWK가 정확히 뭔가
JWK(JSON Web Key) = 암호화 키를 JSON으로 표현하는 포맷.
토큰도 프로토콜도 아니고, PEM 파일의 JSON 버전이라고 생각하면 된다.
{
"kty": "RSA",
"kid": "key-2026-01",
"use": "sig",
"alg": "RS256",
"n": "0vx7agoebGcQ...",
"e": "AQAB"
}
kty: 키 종류 (RSA, EC, OKP...)kid: key ID — 여러 키 중 어느 것으로 검증할지 식별n,e: RSA modulus / exponent, 수학적 키 재료 자체use/alg: 서명용 여부, 알고리즘
계층 정리
| 약어 | 정체 |
|---|---|
| JWT | 토큰 |
| JWS | 그 토큰의 서명 구조 |
| JWK | 검증에 쓰는 키의 포맷 |
| JWKS | 그 키들의 배포 방법 (.well-known/jwks.json) |
검증 흐름
- JWT 헤더에서
kid를 읽는다 - JWKS 엔드포인트(예:
https://<project>.supabase.co/auth/v1/.well-known/jwks.json)에서 같은kid의 JWK를 찾는다 (보통 캐싱) - 그 공개키로 서명을 검증한다
발급자가 새 키를 JWKS에 추가하고 새 토큰부터 새 키로 서명하면,
검증자들은 코드 변경 없이kid매칭만으로 따라온다.
Google 로그인 검증 시googleapis.com/oauth2/v3/certs를 가져오는 것도 같은 패턴.
3. HS256 → RS256: 기밀성이 아니라 무결성+증명
처음에 "대칭키+비대칭키 하이브리드 암호화(ex: TLS)랑 같은 거네"라고 생각했는데,
동기는 같고 메커니즘은 다르다.
- 하이브리드 암호화: 비대칭으로 대칭키 교환 → 데이터는 빠른 대칭키로 암호화. 두 방식의 조합, 목적은 기밀성
- JWT의 HS256 → RS256: 대칭 MAC을 비대칭 서명으로 대체. 목적은 무결성 + 발급자 증명 (payload는 base64라 누구나 읽을 수 있음. 숨기는 게 아니라 "위조되지 않았음"을 보장)
공유하는 뿌리는 "대칭키는 공유해야 해서 관리가 안 된다"는 키 배포 문제:
- HS256: 검증자 전원이 secret 보유 → 하나만 털려도 토큰 위조 가능 (secret으로 서명까지 할 수 있으므로)
- RS256: 공개키가 유출돼도 검증밖에 못 함 → 발급 권한은 발급자에게만
RS256 = RSASSA-PKCS1-v1_5 서명 + SHA-256 해시. ES256이면 RSA 대신 타원곡선(ECDSA P-256).
4. Supabase가 JWT를 쓸 수밖에 없는 이유: 분산 아키텍처
생각해보면 Supabase는 단일 서버가 아니라 별도 컴포넌트들의 묶음이다.
로컬에서 supabase start 하면 Docker 컨테이너가 대여섯 개 뜨는 이유.
Kong (API 게이트웨이) ── 모든 요청의 입구
├─ GoTrue /auth/v1/* 인증 서버 (Go)
├─ PostgREST /rest/v1/* 테이블 → REST API
├─ Realtime /realtime/v1/* WebSocket 구독 (Elixir)
├─ Storage API /storage/v1/*
└─ PostgreSQL ── 모두가 공유하는 DB
요청마다 각 컴포넌트가 Auth 서버에 "이 세션 유효해?"라고 물어보면 병목
→ 각자 토큰만 보고 로컬 검증해야 함 → JWT의 원래 용도.
Supabase도 원래는 HS256 공유 secret이 기본이었고,
JWKS 기반 비대칭 검증은 비교적 최근에 추가·권장되기 시작했다고 한다..
GoTrue의 정체
- Netlify가 만든 Go 기반 인증 마이크로서비스를 Supabase가 포크 → 현재는
supabase/auth리포로 독자 발전, 공식 명칭은 Supabase Auth auth스키마는 GoTrue의 전용 저장소일 뿐, 행위의 주체가 아니다- 회원가입 시: GoTrue가
auth.users에 row 쓰기 → SMTP 인증 메일 발송 → 확인 클릭 받아email_confirmed_at채움 → JWT 서명·발급. OAuth 리다이렉트 왕복도 GoTrue 담당 - Supabase가
auth스키마 직접 수정을 경고하는 이유 = 남의 서비스 내부 상태이기 때문
RLS와 연결되는 지점
PostgREST는 요청의 JWT를 스스로 검증하고, payload의 sub(user ID)와 role을 PostgreSQL 세션 변수로 설정 → RLS 정책이 auth.uid()로 그 값을 읽어 행 단위 접근 제어. GoTrue는 발급에만 등장하고 검증은 분산 처리.
5. better-auth와의 대비
| Supabase (GoTrue) | better-auth | |
|---|---|---|
| 형태 | 별도 인증 서버를 빌려 씀 | 인증 로직을 내 서버에 심음 |
| 검증 주체 | 각 컴포넌트가 JWT 분산 검증 | 내 Next.js 프로세스가 DB 세션 조회 |
| 기본값 | JWT + JWKS | 쿠키 + DB 세션 |
| 인증 메일 | GoTrue가 SMTP로 직접 발송 | sendVerificationEmail 콜백에 Resend/SES 직접 연결 |
| JWT 지원 | 태생부터 JWT | 플러그인으로 JWKS 엔드포인트 노출 (외부 서비스 검증용) |
'TIL' 카테고리의 다른 글
| [260723 TIL] Wappalyzer 쓰지말자(OAuth 콜백 유출 사건) (1) | 2026.07.24 |
|---|---|
| [260531 TIL] 멀티가능 미니 FPS 만들기 2편(운영) (0) | 2026.05.31 |
| [260531 TIL] 멀티가능 미니 FPS 만들기 1편(기본구현) (1) | 2026.05.31 |
| [260528 TIL] Next.js Vercel → AWS 마이그레이션 (0) | 2026.05.28 |
| [260527 TIL] 비개발자에게 Claude로 RDS 조회할 수 있도록(안전히...) (0) | 2026.05.27 |