JWT 토큰이란 무엇이고 access token과 refresh token은 어떻게 관리해야 할까
프론트엔드 개발을 하다 보면 인증을 구현하는 순간 거의 반드시 마주치는 단어들이 있습니다.
JWTaccess tokenrefresh tokenHttpOnly cookie- 토큰 만료
- 자동 재발급
그런데 막상 실무로 들어가면 여기서부터 헷갈리기 시작합니다.
JWT가 곧 인증 방식인지access token과refresh token은 왜 둘 다 필요한지- 토큰을
localStorage에 둬도 되는지 - 왜 어떤 팀은 쿠키를 쓰고 어떤 팀은 메모리에 두는지
- 토큰 만료 시 재발급 흐름은 어디서 처리해야 하는지
이 주제는 단순히 개념을 외우는 것으로 끝나지 않습니다. 중요한 것은 토큰이 무엇인지보다, 어떤 위협을 전제로 어디에 두고 어떻게 갱신할 것인지를 설계하는 일입니다.
이 글에서는 JWT와 토큰 관리 방법을 아래 흐름으로 정리해보겠습니다.
JWT는 무엇인지access token과refresh token은 왜 나뉘는지- 프론트엔드에서 토큰을 어디에 둘 수 있는지
- 어떤 저장 방식이 언제 더 적합한지
- 실제 재발급 흐름은 어떻게 설계하는지
- 실무에서 자주 하는 실수는 무엇인지
한눈에 보면
먼저 짧게 정리하면 이렇습니다.
JWT는 토큰 형식 중 하나이지, 인증 그 자체와 완전히 같은 말은 아닙니다.- 보통
access token은 짧게,refresh token은 더 길게 가져갑니다. access token은 API 호출 권한을 나타내고,refresh token은 새access token을 발급받기 위한 자격에 가깝습니다.- 프론트엔드에서는 토큰 저장 위치가 곧 보안 모델이 됩니다.
- 실무에서는
refresh token은HttpOnly쿠키로,access token은 메모리 또는 짧은 수명 기반으로 다루는 전략이 자주 쓰입니다.
즉, 핵심은 "JWT를 쓸까?"보다 토큰 노출 범위를 어떻게 줄이고, 만료 이후 사용자 경험을 어떻게 설계할까에 더 가깝습니다.
JWT는 정확히 무엇일까?
JWT는 JSON Web Token의 줄임말입니다.
이름만 보면 거창해 보이지만, 가장 단순하게 말하면 JSON 형태의 클레임을 담고 서명한 토큰 형식입니다.
보통 생김새는 이렇게 보입니다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MDAwMDAwMDB9
.signature대개 세 부분으로 나뉩니다.
- header
- payload
- signature
이 중 payload에는 이런 정보가 들어갈 수 있습니다.
- 사용자 id
- 권한(role)
- 발급 시각
- 만료 시각
즉, JWT는 서버가 "이 토큰은 내가 발급했고, 안의 내용이 위조되지 않았다"는 것을 검증할 수 있게 해주는 형식입니다.
여기서 중요한 점이 하나 있습니다. JWT는 암호화된 안전 금고가 아니라, 서명된 데이터 형식에 더 가깝다는 점입니다.
즉:
- 위조 방지는 가능하지만
- payload가 누구에게도 안 보인다는 뜻은 아니고
- 민감한 정보를 마음대로 넣어도 된다는 뜻도 아닙니다
그래서 JWT payload에는 너무 민감한 값을 직접 담지 않는 편이 일반적입니다.
JWT가 곧 인증 방식일까?
실무에서 아주 자주 생기는 오해입니다.
많은 경우 JWT 인증이라고 부르지만, 정확히 보면 JWT는 토큰의 표현 형식에 가깝습니다.
인증 시스템 전체를 보면 사실 더 많은 요소가 들어갑니다.
- 로그인은 어떻게 하는가
- 토큰은 어디에 저장하는가
- 서버는 토큰을 어떻게 검증하는가
- 만료 시 어떻게 재발급하는가
- 로그아웃 시 어떤 상태를 무효화하는가
즉, JWT를 쓴다고 해서 인증 문제가 자동으로 해결되는 것은 아닙니다. 오히려 실무에서는 "JWT를 어디에 저장하고 어떤 수명으로 운영할 것인가"가 더 중요할 때가 많습니다.
왜 access token과 refresh token을 나눌까?
이 부분이 핵심입니다.
처음에는 이런 생각을 하기 쉽습니다.
"그냥 로그인할 때 토큰 하나 주고 오래 쓰면 안 되나?"
물론 기술적으로는 가능할 수 있습니다. 하지만 보안과 사용자 경험을 같이 보면 문제가 생깁니다.
토큰 하나만 오래 쓰면 생기는 문제
- 한 번 탈취되면 오래 악용될 수 있고
- 만료 시간을 짧게 주면 사용자가 자주 다시 로그인해야 하고
- 만료 시간을 길게 주면 노출 사고의 피해 반경이 커집니다
그래서 보통 역할을 나눕니다.
access token
- 실제 API 요청 권한을 담는 토큰
- 수명이 짧음
- 자주 보내는 토큰
refresh token
- 새
access token을 발급받기 위한 토큰 - 수명이 더 김
- 상대적으로 더 조심해서 다뤄야 하는 토큰
즉, 이 구조는 이렇게 읽는 편이 좋습니다.
access token: 자주 쓰지만 짧게refresh token: 덜 쓰지만 더 신중하게
결국 둘을 나누는 이유는 노출 위험과 사용자 경험 사이의 균형입니다.
access token은 어떤 역할을 할까?
보통 프론트엔드가 API를 호출할 때 아래처럼 보냅니다.
Authorization: Bearer <access_token>서버는 이 토큰을 보고:
- 이 요청이 누구 것인지
- 어떤 권한을 갖는지
- 토큰이 아직 유효한지
를 판단합니다.
즉, access token은 실제 요청마다 붙는 권한 증명에 가깝습니다.
그래서 이 토큰은:
- 자주 네트워크를 타고
- 브라우저 코드에서 참조될 가능성이 있고
- 탈취되면 곧바로 API 호출에 악용될 수 있습니다
따라서 수명을 짧게 가져가는 것이 일반적입니다.
예를 들어:
- 5분
- 10분
- 15분
- 30분
처럼 비교적 짧은 만료 시간을 두는 편이 많습니다.
refresh token은 어떤 역할을 할까?
refresh token은 직접 모든 API 요청에 붙는 토큰이 아닙니다.
대신 보통 아래 같은 흐름에서 사용됩니다.
access token이 만료됨- 프론트엔드가 재발급 엔드포인트 호출
- 서버가
refresh token을 검증 - 유효하면 새
access token발급
즉, refresh token은 세션을 이어가기 위한 재발급 자격 증명에 가깝습니다.
그래서 refresh token은:
- 자주 노출되면 안 되고
- 일반 API 요청마다 반복 전송하는 것이 아니라
- 재발급 시점에만 제한적으로 쓰는 편이 더 안전합니다
이 특성 때문에 실무에서는 refresh token을 HttpOnly 쿠키에 넣는 전략이 자주 나옵니다.
왜 refresh token이 더 민감할까?
많은 사람이 access token이 더 자주 쓰이니까 더 중요하다고 느끼지만, 운영 관점에서는 refresh token이 더 민감하게 다뤄지는 경우가 많습니다.
이유는 단순합니다.
access token은 짧게 만료되지만refresh token은 세션을 더 오래 이어줄 수 있기 때문입니다
즉, refresh token이 탈취되면 공격자는 새 access token을 계속 재발급받아 세션을 연장할 수 있습니다.
그래서 보통 원칙은 이렇습니다.
access token: 짧게, 자주 바뀌게refresh token: 더 안전한 저장 방식과 더 엄격한 검증
토큰은 프론트엔드에서 어디에 저장할 수 있을까?
실무에서 가장 많이 나오는 질문입니다.
대표적으로 아래 선택지가 있습니다.
- 메모리 변수
localStoragesessionStorage- 쿠키
각 방식은 장단점이 분명합니다.
1. localStorage
가장 쉽게 떠올리기 쉬운 방식입니다.
localStorage.setItem('accessToken', token);장점은:
- 구현이 단순하고
- 새로고침 뒤에도 값이 남고
- 브라우저에서 접근이 쉽다는 점입니다
하지만 단점도 큽니다.
- 자바스크립트에서 쉽게 읽을 수 있고
- XSS가 발생하면 탈취 표면이 커집니다
즉, localStorage는 편하지만, 스크립트로 읽을 수 있는 영역에 민감한 토큰을 오래 두는 방식이라는 점을 항상 같이 봐야 합니다.
그래서 보안 민감도가 높은 서비스에서는 refresh token까지 localStorage에 두는 것은 보수적으로 봐야 합니다.
2. sessionStorage
localStorage와 비슷하지만 브라우저 탭 세션 기준으로 유지됩니다.
장점은:
- 탭 단위로 관리되며
- 브라우저 전체에 오래 남기지 않을 수 있다는 점입니다
하지만 본질적인 보안 특성은 크게 다르지 않습니다.
- 역시 자바스크립트에서 읽을 수 있고
- XSS에 노출되면 탈취될 수 있습니다
즉, 지속성의 차이는 있지만 스크립트 접근 가능성이라는 핵심 리스크는 그대로입니다.
3. 메모리 저장
예를 들어:
let accessToken: string | null = null;혹은 상태 관리 라이브러리나 React context 안에만 두는 방식입니다.
장점은:
- 새로고침하면 사라지고
- 브라우저 영속 저장소에 직접 남지 않으며
- 노출 반경을 상대적으로 줄일 수 있다는 점입니다
하지만 단점도 있습니다.
- 새로고침 후 복원이 어렵고
- 초기 재인증 또는 재발급 흐름이 따로 필요합니다
그래서 이 방식은 보통:
access token은 메모리에 두고- 새로고침 후에는
refresh token기반으로 재발급
하는 전략과 함께 자주 쓰입니다.
4. 쿠키
쿠키는 토큰 저장 전략에서 가장 많이 논쟁되는 방식입니다.
특히 중요한 것은 아래 옵션입니다.
HttpOnlySecureSameSite
HttpOnly 쿠키
이 옵션이 붙은 쿠키는 자바스크립트에서 직접 읽을 수 없습니다.
즉:
document.cookie로 읽히지 않고- XSS가 있어도 토큰을 스크립트로 바로 꺼내가기 어렵습니다
이 특성 때문에 refresh token 저장 위치로 자주 추천됩니다.
하지만 쿠키가 만능은 아니다
쿠키를 쓰면 CSRF 같은 다른 관점을 같이 봐야 합니다.
즉, 보안은 늘 교환입니다.
- 스크립트 접근을 줄이면
- 자동 전송 특성 때문에 다른 방어도 필요해질 수 있습니다
그래서 실무에서는 보통 쿠키 자체보다 쿠키 옵션과 서버 검증 정책까지 같이 설계해야 안전합니다.
프론트엔드 기준으로 많이 쓰는 전략은 무엇일까?
실무에서는 완전히 하나의 정답이 있는 것은 아니지만, 비교적 자주 보이는 방향은 있습니다.
전략 1. access token과 refresh token을 둘 다 스토리지에 저장
구현은 쉽습니다.
하지만 보안상으로는 보수적으로 봐야 합니다.
- 둘 다 자바스크립트에서 읽을 수 있고
- XSS가 나면 피해 범위가 커질 수 있기 때문입니다
전략 2. access token은 메모리, refresh token은 HttpOnly 쿠키
이 조합은 실무에서 많이 언급됩니다.
이유는:
access token은 짧게 쓰고 메모리에서만 관리하고refresh token은 자바스크립트가 못 읽는 쿠키에 두고- 새로고침 후에는 재발급 엔드포인트로 세션을 복원할 수 있기 때문입니다
즉, 노출을 줄이면서도 사용자 경험을 유지하기 쉬운 절충안으로 자주 선택됩니다.
전략 3. 서버 세션 중심 + 쿠키 기반 인증
이 경우 프론트엔드가 토큰을 직접 다루지 않는 비중이 더 커질 수 있습니다.
이 전략은 단순할 수 있지만, 서버 구조와 배포 환경에 따라 선택이 달라집니다.
즉, 중요한 것은 "JWT를 쓰느냐" 하나가 아니라:
- 서버가 어떤 인증 모델을 쓰는지
- 브라우저 보안 모델과 맞는지
- 운영 난이도를 감당할 수 있는지
를 함께 보는 것입니다.
실제 재발급 흐름은 어떻게 설계할까?
실무에서는 대개 이런 흐름을 보게 됩니다.
기본 흐름
- 로그인 성공
- 서버가
access token발급 - 서버가
refresh token도 발급 - 프론트엔드는
access token으로 API 호출 - 만료 시 재발급 엔드포인트 호출
- 성공하면 새
access token저장 후 원래 요청 재시도
흐름으로 보면 대략 이런 느낌입니다.
로그인
-> access token 발급
-> refresh token 발급
API 호출
-> access token 만료
-> refresh endpoint 호출
-> 새 access token 수신
-> 원래 요청 재시도핵심은 단순합니다. 사용자는 매번 로그인하지 않지만, 내부적으로는 짧은 access token을 계속 갱신하는 구조입니다.
재발급 로직에서 무엇을 조심해야 할까?
여기가 실무에서 가장 자주 깨집니다.
1. 동시 요청 경쟁 상태
여러 API 요청이 동시에 나갔는데 모두 만료된 토큰을 들고 있다면, 재발급 요청이 한꺼번에 여러 번 발생할 수 있습니다.
그러면:
- 재발급이 중복 호출되고
- 어떤 요청은 오래된 토큰으로 다시 시도하고
- 어떤 요청은 실패 처리되어 로그아웃되는 등
흐름이 꼬일 수 있습니다.
그래서 보통은:
- 재발급 요청은 한 번만 보내고
- 나머지 요청은 대기시키고
- 새 토큰을 받은 뒤 순서대로 재시도하는 큐 전략
을 둡니다.
2. 무한 재시도
재발급 요청 자체가 실패하는데도 계속 재시도하면 무한 루프가 생길 수 있습니다.
예를 들어:
access token만료- 재발급 시도
refresh token도 만료됨- 다시 재발급 시도
같은 흐름이 되면 안 됩니다.
그래서 보통:
- 재발급 요청에는 재시도 제한을 두고
- 실패 시 명확하게 로그아웃 처리하거나
- 로그인 페이지로 보내는 종료 조건을 둡니다
3. 원래 요청 재시도 조건
모든 실패를 토큰 만료로 보면 안 됩니다.
- 네트워크 오류
- 권한 없음
- 서버 내부 오류
- 실제 인증 실패
를 구분해야 합니다.
즉, 401이 왔다고 해서 항상 기계적으로 재발급하는 것이 아니라, 정말 재발급이 맞는 상황인지를 분기해야 합니다.
4. 로그아웃 시 정리
로그아웃할 때는 단순히 프론트 상태만 비우면 부족할 수 있습니다.
- 메모리에 든
access token제거 - 관련 캐시 제거
- 서버 측
refresh token무효화 - 쿠키 정리
까지 같이 가야 세션 종료가 더 명확해집니다.
refresh token은 매번 재발급해야 할까?
이 부분도 실무에서 많이 갈립니다.
보통 두 흐름이 있습니다.
1. 고정된 refresh token
같은 refresh token을 만료 전까지 계속 쓰는 방식입니다.
구현은 단순하지만, 탈취 이후 장기간 악용 위험을 더 크게 볼 수도 있습니다.
2. refresh token rotation
재발급할 때마다 새 refresh token도 함께 발급하는 방식입니다.
즉:
- 이전
refresh token은 폐기하고 - 새 토큰으로 교체해가며
- 탈취 이후 재사용 탐지나 세션 통제를 더 엄격하게 가져갈 수 있습니다
운영은 더 복잡하지만, 보안적으로는 더 강한 전략으로 보는 경우가 많습니다.
즉, 민감한 서비스일수록 refresh token도 그냥 오래 두기보다 회전(rotation)과 서버 저장 상태 관리까지 같이 보는 편이 낫습니다.
JWT payload에는 무엇을 넣고 무엇을 피해야 할까?
실무에서 종종 payload에 너무 많은 정보를 넣으려는 경우가 있습니다.
하지만 보통은 최소한으로 두는 편이 낫습니다.
예를 들어 많이 들어가는 값:
sub또는 사용자 id- 권한 정보
iatexp
반대로 조심할 값:
- 민감한 개인정보
- 비밀번호나 복구 정보
- 너무 많은 비즈니스 데이터
즉, JWT는 편리하다고 해서 사용자 정보를 잔뜩 싣는 운반체로 쓰기보다, 인증 판단에 필요한 최소 정보만 담는 편이 일반적입니다.
프론트엔드에서 자주 하는 실수
정리하면 아래 실수가 정말 자주 나옵니다.
JWT자체가 안전하니 어디에 저장해도 된다고 생각한다refresh token까지localStorage에 오래 보관한다- 토큰 payload에 민감한 정보를 과하게 담는다
- 재발급 로직에 동시 요청 제어가 없다
- 재발급 실패 시 종료 조건이 없어 무한 루프가 생긴다
- 쿠키를 쓰면서
SameSite,Secure, CSRF 대응을 같이 보지 않는다 - 로그아웃 시 서버 무효화와 클라이언트 정리를 함께 하지 않는다
즉, 문제는 토큰 형식보다도 저장 위치, 수명, 재발급 흐름, 실패 처리에서 더 자주 발생합니다.
실무 체크리스트
실제로 설계할 때는 아래 질문으로 빠르게 점검하면 도움이 됩니다.
access token과refresh token의 역할과 수명을 명확히 나눴는가?refresh token을 자바스크립트에서 직접 읽지 않아도 되는 구조인가?- 토큰 저장 위치가 XSS, CSRF 관점에서 어떤 트레이드오프를 가지는지 이해하고 있는가?
- 재발급 요청이 동시에 여러 번 발생할 때 제어 전략이 있는가?
- 재발급 실패 시 로그아웃이나 세션 종료 조건이 분명한가?
- 로그아웃 시 서버와 클라이언트 상태를 함께 정리하는가?
- 민감한 서비스라면
refresh token rotation까지 검토했는가?
이 질문에 답할 수 있으면, 적어도 "그냥 토큰 하나 저장해두고 쓰는 인증"보다는 훨씬 안정적인 구조에 가까워집니다.
정리하면
JWT를 한 줄로 줄이면, 서명된 JSON 기반 토큰 형식입니다.
실무 기준으로 기억할 핵심은 이렇습니다.
JWT는 인증 시스템 전체가 아니라 그 안에서 쓰이는 토큰 형식 중 하나이고access token은 짧게,refresh token은 더 신중하게 다뤄야 하며- 프론트엔드에서는 토큰 저장 위치가 곧 보안 모델이 되고
- 실무에서는
access token은 메모리,refresh token은HttpOnly쿠키로 두는 조합이 자주 검토되며 - 재발급 흐름에서는 동시 요청, 무한 재시도, 로그아웃 정리까지 함께 설계해야 합니다
결국 중요한 것은 "JWT를 쓸 줄 아는가"가 아니라, 세션을 어떤 위협 모델 위에서 얼마나 안전하고 자연스럽게 운영할 것인가입니다. 이 기준이 서야 localStorage, 쿠키, 메모리, rotation, 재발급 전략도 각각 왜 필요한지 더 선명하게 보이게 됩니다.
