jhpka's blog

[JWT] JWT 인증 시스템 가이드

Admin User

JWT 구조

JWT는 3개의 파트로 구성됩니다:

text
header.payload.signature

예시:

text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1YWJjZDEyMzQ1Njc4OTAiLCJ1c2VybmFtZSI6ImpvaG4iLCJwcm92aWRlciI6ImxvY2FsIiwiZW1haWwiOiJqb2huQGV4YW1wbGUuY29tIiwiaWF0IjoxNzAwMDAwMDAwLCJleHAiOjE3MDAwMDA5MDB9.서명부분

중요: JWT의 payload는 암호화가 아닌 인코딩입니다. 누구나 디코딩해서 내용을 볼 수 있으며, signature 부분만 JWT_SECRET으로 서명되어 위변조 방지 역할을 합니다.


JWT Payload 내용

이 프로젝트의 Access Token payload:

필드설명
id사용자의 MongoDB ObjectId
username사용자명
provider인증 방식 (local, google, apple 등)
email이메일 주소
exp토큰 만료 시간
iat토큰 발급 시간

토큰 만료 시간 설정

토큰환경변수기본값
Access TokenSESSION_EXPIRY15분
Refresh TokenREFRESH_TOKEN_EXPIRY7일

이중 토큰 시스템 (Access + Refresh)

Access Token

  • 짧은 만료 시간 (15분)
  • 클라이언트 메모리에 저장
  • 모든 API 요청의 Authorization 헤더에 포함

Refresh Token

  • 긴 만료 시간 (7일)
  • httpOnly 쿠키로 저장 (JavaScript 접근 불가)
  • secure 플래그 (HTTPS에서만 전송)
  • sameSite=strict (CSRF 방어)
  • Access Token 갱신에만 사용

토큰 갱신 흐름 (서버 응답 기반)

text
클라이언트 API 요청
        ↓
서버: "토큰 만료됨" (401 Unauthorized)
        ↓
axios interceptor가 401 감지
        ↓
자동으로 /api/auth/refresh 호출 (Refresh Token 쿠키 포함)
        ↓
새 Access Token 수신
        ↓
원래 요청 자동 재시도
        ↓
사용자에게 결과 전달 (seamless)

서버 응답 기반 vs 클라이언트 선제 확인

항목서버 응답 기반클라이언트 선제 확인
구현 복잡도단순복잡 (JWT 디코딩, 시간 계산)
시간 동기화서버 시계만 기준클라이언트 시계 의존 (오차 가능)
서버 정책 변경클라이언트 수정 불필요수정 필요할 수 있음
토큰 강제 만료 대응즉시 대응exp 기준이라 대응 불가
네트워크 효율실패 시 재요청 필요미리 갱신하면 재요청 없음

보안 관점

JWT 탈취 시 위험

JWT는 stateless 방식이라 탈취 시 만료 전까지 사용 가능합니다.

보안 대책

  1. 짧은 Access Token 만료 시간: 15분으로 피해 범위 제한
  2. Refresh Token 보호:
    • httpOnly: XSS 공격으로 JavaScript 접근 불가
    • secure: HTTPS에서만 전송
    • sameSite=strict: CSRF 공격 방어
  3. 세션 기반 Refresh Token: DB에서 세션 삭제 시 갱신 차단

Refresh Token 탈취 난이도

Refresh Token을 탈취하려면:

  • 피해자 기기에 물리적 접근
  • 네트워크 MITM 공격 (HTTPS라 어려움)
  • 브라우저 취약점 악용
  • 악성 브라우저 확장 프로그램

로그아웃 처리

javascript
const logoutUser = async (req, refreshToken) => {
  // 1. DB에서 세션 삭제
  const session = await findSession({ userId, refreshToken });
  await deleteSession({ sessionId: session._id });

  // 2. Express 세션 파괴
  req.session.destroy();
};
항목위치처리
Refresh Token 세션서버 DBdeleteSession()으로 삭제
클라이언트 쿠키브라우저서버가 만료된 쿠키로 덮어씀
Access Token클라이언트 메모리상태 초기화

로그아웃 후에는 탈취된 Refresh Token도 DB에 세션이 없어 무효화됩니다.


자동 로그인

"자동 로그인" = Refresh Token 만료 시간이 길게 설정됨

서비스 유형Refresh Token 만료특징
은행/금융30분 ~ 1일보안 중시
일반 웹서비스7일 ~ 30일적당한 균형
SNS/스트리밍30일 ~ 1년편의성 중시

자동 로그인 흐름

text
Day 1: 로그인 → Refresh Token (7일 유효)
Day 2: 브라우저 열기 → Access Token 없음 → Refresh로 갱신 → 로그인 유지
Day 3: 같은 방식...
Day 7: Refresh Token 만료 → 다시 로그인 필요

"로그인 유지" 체크박스 구현 방식

javascript
// 방법 1: 만료 시간 분기
if (rememberMe) {
  expiration = 30일;
} else {
  expiration = 1일;
}

// 방법 2: 세션 쿠키 사용
if (rememberMe) {
  cookie.expires = 30일;
} else {
  cookie.expires = undefined;  // 브라우저 닫으면 삭제
}

관련 파일 위치

파일역할
api/models/userMethods.jsAccess Token 생성 (generateToken)
api/models/Session.jsRefresh Token 및 세션 관리
api/server/services/AuthService.js인증 서비스 (로그인, 로그아웃, 토큰 설정)
api/server/controllers/AuthController.js인증 컨트롤러 (refresh 엔드포인트)
packages/data-provider/src/request.ts클라이언트 axios interceptor (자동 토큰 갱신)
댓글을 불러오는 중...