[앱인토스] 토스 로그인과 JWT, 14일의 비밀
토스 로그인과 JWT,
14일의 비밀을 파헤치다
앱인토스 미니앱을 만들다 보면 토스 로그인을 반드시 구현해야 한다. 그런데 토큰이 세 종류나 등장하고, 유효기간도 제각각이라 처음엔 꽤 혼란스럽다.
이번 글에서는 호텔 키카드 비유를 통해 토스 로그인의 토큰 구조를 아주 쉽게 정리해본다.
이런일이 발생했음
======================================================================
JWT 7일 만료
↓
check API → 401
↓
.catch(()=>{}) 로 에러 삼킴
↓
received = undefined → falsy
↓
"아직 안 받았네?" → SDK 직접 grant 호출
↓
포인트 지급 → 근데 서버 log도 401 → DB 미기록
↓
다음 요청에서 또 grant → 무한 출혈 💸
======================================================================
비유: 호텔 체크인 과정
당신이 호텔에 처음 도착하면 신분증을 제시해서 신원을 확인받는다. 프런트는 신분증을 복사하거나 보관하지 않는다. 딱 한 번 확인하고, 대신 객실 키카드를 발급해준다. 이후에는 신분증 없이 키카드만으로 객실 출입이 가능하다.
토스 로그인도 완전히 똑같다.
- 신분증 = 토스 계정 (이미 토스에 가입된 유저)
- 호텔 프런트 = 토스 서버 (신원을 확인해주는 기관)
- 객실 키카드 = 우리 서버가 발급하는 JWT
- 체크인 확인서 = authorizationCode (딱 10분짜리)
토큰이 총 몇 가지야?
결론부터 말하면 4가지다. 하지만 클라이언트(앱)가 실제로 들고 다니는 건 마지막 두 가지뿐이다.
| 토큰 | 발급 주체 | 유효시간 | 역할 |
|---|---|---|---|
authorizationCode |
토스 | 10분 | 서버에 전달하는 일회용 입장권 |
토스 accessToken |
토스 | 1시간 | 토스 API 호출용 (서버만 사용) |
토스 refreshToken |
토스 | 14일 | 토스 accessToken 재발급용 (서버만 사용) |
우리 JWT accessToken |
우리 서버 | 7일 | 우리 API 호출용 (앱이 들고 다님) |
우리 JWT refreshToken |
우리 서버 | 13일 | 우리 JWT 재발급용 (앱이 들고 다님) |
전체 로그인 흐름
왜 토스 토큰을 버리는 걸까?
호텔 프런트(토스)는 신분증(토스 계정)을 확인하고 "이 분 맞습니다"라고 확인서를 발급해준다. 이후 우리 서비스는 그 확인서를 바탕으로 우리만의 객실 키카드(JWT)를 만든다. 프런트의 인감도장(토스 토큰)은 이미 역할이 끝났으니 필요 없다.
덕분에 이후 모든 API 호출(/api/attendance, /api/promotion 등)은 토스 서버를 거치지 않고 우리 서버 안에서만 처리된다. 속도도 빠르고 토스 의존성도 최소화된다.
14일마다 재인증이 필요한 이유
이제 핵심 질문이다. "그러면 14일마다 뭘 다시 확인하는 건가?"
대답은 간단하다. "이 사람이 아직도 유효한 토스 유저인지" 재확인하는 것이다. 토스에서 탈퇴했거나 서비스 연결을 끊었을 수도 있기 때문이다.
13일? 왜 14일이 아니라 13일?
토스 refreshToken이 14일에 만료된다. 우리 JWT refresh를 13일로 설정하는 이유는, 우리가 먼저 만료를 감지해서 토스 토큰이 아직 살아있을 때 재인증을 처리하기 위해서다.
재인증은 유저가 알아채나?
아니다. 토스 앱의 특성상 appLogin()을 호출해도 "이미 로그인된 유저는 창이 뜨지 않고 즉시 인가 코드가 반환"된다.
키카드(JWT)가 13일째에 만료되면, 앱이 자동으로 프런트(토스)에 다시 신분증을 보여준다. 프런트는 이미 아는 손님이라 바로 새 키카드를 발급해준다. 손님은 문이 열리지 않는다고 느끼기 전에 새 카드가 생긴다.
JWT는 서버에 저장하나?
아니다. 이것이 JWT의 가장 중요한 특징이다. JWT는 클라이언트(앱)의 localStorage에 저장되고, 서버는 아무것도 저장하지 않는다.
서버는 요청이 오면 딱 세 가지만 확인한다.
- JWT 서명이 유효한가? (위조 여부)
- 만료 시간(
exp)이 지나지 않았는가? - 이 유저가 연동 해제했는가? (
token_revoked_at)
DB 조회도 없고, 세션 저장도 없다. 그래서 Vercel 서버리스 환경에서도 문제없이 동작한다.
사람마다 만료 날짜가 다른 이유
JWT 안에는 iat(발급 시각)과 exp(만료 시각)이 포함되어 있다. 따라서 로그인한 시점이 다르면 만료 시점도 자동으로 달라진다.
A 유저 → 4월 1일 로그인 → 4월 14일에 자동 재인증
B 유저 → 4월 15일 로그인 → 4월 28일에 자동 재인증
서버는 이를 추적할 필요가 없다. 토큰 자체가 언제 만료되는지 스스로 담고 있기 때문이다.
정리
| 질문 | 답 |
|---|---|
| 토스 API는 언제만 호출하나? | 최초 로그인 + 13일마다 재인증 시 |
| 앱이 들고 다니는 토큰은? | 우리 JWT access (7일) + refresh (13일) |
| JWT를 서버에 저장하나? | 아니다. 클라이언트 localStorage에만 |
| 재인증 시 유저가 뭘 하나? | 아무것도 안 해도 됨. 자동 처리 |
| 13일? 14일 아닌가? | 토스 토큰이 살아있을 때 처리하기 위해 하루 먼저 |