본문으로 건너뛰기
yutils

로그인은 어떻게 동작할까? (session · JWT · OAuth · passkey)

session cookie · JWT · OAuth flow · passkey — 각 모델이 무엇을 저장하고 신뢰는 어디에 있는지, 왜 refresh token 이 필요한지, WebAuthn 의 passkey 가 어떻게 비밀번호를 완전히 대체하는지.

약 9분 읽기

로그인 후 매 요청마다 "이 사용자가 누구지?" 를 어떻게 알까? cookie 의 session ID? JWT? OAuth 의 access token? 사용자가 비밀 번호 안 치고 Apple / Google 로 로그인 가능한 건 어떻게? 그리고 2024 년부터 보편화된 passkey 는 비밀번호를 어떻게 완전히 대체 하나? 이 가이드는 4 가지 authentication 모델 — session / JWT / OAuth / passkey — 의 실제 메카니즘을 정리한다.

모델 1 — Session Cookie (가장 전통적)

1. 로그인:
   POST /login
   {username, password}
   ↓
   Server:
   - bcrypt 검증
   - random session ID 생성 (예: "abc123...")
   - DB / Redis 에 {sessionId: userId} 저장
   - Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax

2. 후속 요청:
   Cookie: session=abc123
   ↓
   Server:
   - DB lookup: sessionId 으로 userId 찾기
   - request.user = User{...}

3. 로그아웃:
   - DB 에서 session 삭제
   - Set-Cookie: session=; expires=0

장점:

  • session ID 가 opaque (의미 없는 random)
  • 서버가 즉시 revoke 가능 (DB 에서 삭제)
  • cookie 의 HttpOnly / Secure / SameSite 로 보안 안전
  • 사용자 정보 변경 즉시 반영 (매 요청마다 DB 조회)

단점:

  • 매 요청마다 DB 조회 (Redis 로 완화)
  • multiple server 간 session 공유 필요 (sticky session 또는 shared store)
  • scale-out 어려움

모델 2 — JWT (JSON Web Token)

1. 로그인:
   POST /login
   {username, password}
   ↓
   Server:
   - bcrypt 검증
   - JWT 생성: header.payload.signature
     payload = {sub: "user-42", exp: 1700000000}
     signature = HMAC-SHA256(header.payload, SECRET)
   - 응답: {token: "eyJ..."}

2. 후속 요청:
   Authorization: Bearer eyJ...
   ↓
   Server:
   - signature 검증 (SECRET 으로)
   - exp 만료 체크
   - request.user = {id: payload.sub}
   ↑ DB lookup 불필요

실험 — JWT 디코더 가 JWT 의 header / payload / signature 분리 표시. signature 검증은 HMAC 생성기 와 같은 알고리즘.

JWT의 장점·단점

장점:

  • stateless — server 가 session storage 불필요. 여러 server / microservice 간 공유 쉬움
  • self-contained — payload 에 user 정보 박힘
  • mobile app · API · 분산 시스템에 적합

단점:

  • revoke 어려움 — token 자체가 유효하면 만료 전 까지 사용 가능. blacklist 만들면 DB 부담 (그러면 stateless 의미 ↓)
  • payload 길어짐 (cookie 의 ~30 byte 가 JWT 는 ~500 byte)
  • payload 가 base64 — 누구나 디코드. 민감 정보 박지 말 것 (signed ≠ encrypted)
  • secret 유출 시 모든 token 위조 가능 — key rotation 필수

Refresh Token — JWT 의 revoke 문제 완화

로그인 시:
- access token: 짧은 만료 (15 분), client storage
- refresh token: 긴 만료 (30 일), HttpOnly cookie + DB 저장

요청:
1. access token 으로 API 호출 → 만료 시 401
2. refresh token 으로 새 access token 발급
   POST /auth/refresh  (cookie 자동 전송)
   ↓
   Server: DB 의 refresh token 검증 → 유효하면 새 access token
3. 사용자 로그아웃 또는 의심 시: DB 의 refresh token 삭제
   → 다음 갱신 시도 시 거부

Two-token pattern — short-lived access (stateless) + long-lived refresh (stateful). Revoke 가능 + scale-out 친화.

모델 3 — OAuth 2.0 (제 3 자 로그인)

"Apple / Google / GitHub 로 로그인" 의 메카니즘. Authorization Code Flow + PKCE 가 표준:

1. 사용자가 "Sign in with Google" 클릭

2. 우리 site → Google 의 authorize URL:
   https://accounts.google.com/o/oauth2/v2/auth
     ?client_id=...
     &redirect_uri=https://ours.com/callback
     &response_type=code
     &scope=openid email profile
     &state=random_token       ← CSRF 방어
     &code_challenge=...       ← PKCE
     &code_challenge_method=S256

3. 사용자가 Google 에서 로그인 + 우리 site 의 권한 동의

4. Google → 우리 site 의 callback (with code):
   https://ours.com/callback?code=ABC123&state=random_token

5. 우리 server → Google 의 token endpoint:
   POST https://oauth2.googleapis.com/token
   {
     code: "ABC123",
     client_id: "...",
     client_secret: "...",
     code_verifier: "..."      ← PKCE
   }
   ↓
   Google 응답: {
     access_token: "ya29...",     ← Google API 사용 가능
     refresh_token: "...",
     id_token: "eyJ..."           ← JWT, 사용자 정보 포함
   }

6. 우리 server:
   - id_token 의 signature 검증 (Google 의 public key)
   - payload 에서 user 정보 (email, name)
   - 우리 DB 에 user 생성/찾기
   - 우리 session 또는 JWT 발급 → 사용자 로그인 완료

PKCE — Public client 의 안전망

Native app / mobile / SPA 는 client_secret 안전 보관 불가 (코드 리버스 가능). PKCE 가 추가 안전:

Client 가 매번 random code_verifier 생성 (~43 chars)
code_challenge = SHA-256(code_verifier) base64url

Step 2: code_challenge 만 Google 에 보냄 (verifier 보관)
Step 5: code_verifier 함께 보냄 → Google 이 hash 일치 검증

→ 중간자가 code 가로채도 code_verifier 모름 → 사용 불가

OAuth vs OpenID Connect (OIDC)

  • OAuth 2.0 = authorization (권한 부여). "이 user 가 내 Drive 파일 접근 허락"
  • OIDC = OAuth 위 authentication layer. id_token (JWT) 에 사용자 정보 포함. "이 user 의 이메일이 X"

"Sign in with Google" 은 OIDC. 우리는 사용자가 누구인지 알고 싶음, Google Drive 접근 X.

모델 4 — Passkey (WebAuthn)

2022+ 표준. 비밀번호 완전 대체. Apple / Google / Microsoft 가 2024 default 로 push:

1. 가입 (registration):
   - server: random challenge 생성
   - browser → 사용자에게 "이 site 에 passkey 만들까요?" (Touch ID / Face ID)
   - device 가 새 key pair 생성:
     - private key: 디바이스에 저장 (secure enclave)
     - public key: server 에 전송
   - server 가 public key + user 매핑 저장

2. 로그인 (authentication):
   - server: random challenge 생성
   - browser → 디바이스 unlock (Touch ID / Face ID)
   - device 가 private key 로 challenge 서명
   - server 가 저장된 public key 로 signature 검증
   - 성공 → 세션 시작

장점:

  • 비밀번호 X — 입력할 게 없음
  • phishing 불가 — passkey 가 origin 에 묶임. 가짜 site 가 사용 불가
  • DB breach 무력 — server 가 public key 만 저장. 유출돼도 로그인 불가
  • iCloud Keychain / Google Password Manager 가 디바이스 간 sync

단점:

  • 새 디바이스 가입 시 옛 디바이스 또는 backup 필요
  • ecosystem lock-in (Apple / Google 의 cloud sync 가 portable X)
  • 모든 server 가 지원 X (WebAuthn API 구현 필요)

2FA (Two-Factor Authentication)

  • TOTP (Time-based One-Time Password) — Google Authenticator. 30 초마다 새 6-digit. HMAC-SHA1 of current time
  • SMS — 문자로 code. SIM swapping 으로 약함
  • WebAuthn / Security key — YubiKey 같은 hardware key. 가장 강함
  • Passkey = 본질적으로 2FA 의 일종 (디바이스 + biometric)

비교 — 어떤 걸 언제

Session CookieJWTOAuth (OIDC)Passkey
Stateful✅ (DB)❌ (self-contained)❌ (token 위주)✅ (public key 저장)
Revoke즉시어려움refresh token 으로즉시 (public key 삭제)
Scale-outshared store 필요매우 쉬움쉬움매우 쉬움
모바일 / APIcookie 적용 어려움가장 적합가장 적합이상적이지만 도입 진행
UX 비밀번호입력 필요입력 필요외부 provider 로 위임없음
보안표준secret 관리 필수구현 복잡가장 강

실무:

  • 전통 web site — session cookie. 단순, 안전.
  • 모바일 + web — JWT (access + refresh).
  • 제 3 자 로그인 — OAuth/OIDC.
  • 모던 / 보안 민감 — passkey + fallback.

흔한 함정

1. JWT 의 secret 유출

Secret 유출 시 모든 토큰 위조. .env 관리, key rotation, asymmetric key (RS256) 으로 verify only public key 공유.

2. JWT 에 민감 정보

# Bad
payload: {sub: "42", role: "admin", ssn: "..."}
↓ base64url 이지 암호화 X
↓ 누구나 디코드 가능

# Good
payload: {sub: "42"}
↓ 추가 정보는 DB lookup

3. CSRF (Cookie 모델)

Cookie 가 cross-site 자동 전송 → SameSite=Lax/Strict + CSRF token 필요. JWT in Authorization header 는 CSRF 자연 방어.

4. localStorage 의 JWT

XSS 가능성 — attacker JavaScript 가 localStorage 접근. cookie 의 HttpOnly 가 더 안전. 단 CSRF 위험. trade-off.

5. OAuth state parameter 누락

CSRF 공격 가능 — attacker 가 자신의 OAuth code 로 사용자에게 callback 유도. state random token 으로 검증.

6. password reset 의 token 만료

"reset password" link 가 만료 없거나 너무 길면 → email 노출 / steal 시 영구 위험. 15-60 분, one-time use.

참고 자료

요약

  • 4 가지 모델 — Session Cookie / JWT / OAuth (OIDC) / Passkey.
  • Session = stateful + DB lookup, 즉시 revoke. 전통 web 의 default.
  • JWT = stateless + self-contained, scale-out 쉬움. Revoke 어려움 → refresh token pattern.
  • OAuth 2.0 (+ OIDC) = 제 3 자 로그인. PKCE 가 public client (mobile/SPA) 안전. state parameter 로 CSRF 방어.
  • Passkey = 비밀번호 없음 + WebAuthn + 디바이스 biometric. Phishing / DB breach 무력. 2024 default 진입.
  • 2FA — TOTP (Authenticator) / SMS (약함) / WebAuthn (강) / Passkey (이상).
  • 저장 위치 — Cookie (HttpOnly + Secure + SameSite) vs localStorage (XSS 위험). trade-off.
  • 실험 — JWT 디코더 가 JWT 분해, HMAC 생성기 가 signature 알고리즘, Bcrypt 해시 가 password hash.
가이드 목록으로