// 서버: refresh 호출 시 새 refresh 발급 + 옛 것 무효화
app.post('/api/auth/refresh',async(req,res)=>{constold=req.cookies.rt;constsession=awaitdb.sessions.find({refreshToken: old});if(!session||session.revokedAt)returnres.status(401).end();// 옛 것 invalidate
awaitdb.sessions.update(session.id,{revokedAt: newDate()});// 새 발급
constnewRt=signRefresh({userId: session.userId});awaitdb.sessions.create({userId: session.userId,refreshToken: newRt});res.cookie('rt',newRt,cookieOpts);res.json({accessToken: signAccess({userId: session.userId})});});
🤔 의사결정 기준
환경
저장
Web SPA
access in memory + refresh in httpOnly cookie
SSR (Next.js)
session cookie + 서버에서 access 발급
Mobile (RN/iOS/Android)
secure storage (Keychain/Keystore)
서버-서버
짧은 access, OAuth client credentials
❌ 안티패턴
localStorage 에 access token 저장: XSS 한 방으로 탈취. 사고.
Refresh token rotation 안 함: 한 번 탈취되면 영원히 유효. rotation + reuse detection.
JWT signature 만 검증, 만료 안 봄: 만료 토큰 통과. exp / nbf 모두 검증.
JWT payload 에 비밀 정보: payload 는 base64 — 누구나 디코드. 비밀 X.
장시간 access token (24h+): 탈취 시 피해 커짐. 짧게 + refresh.
로그아웃 시 토큰 무효화 안 함: stateless 라 어렵지만 refresh 무효화는 DB 레벨로.
CSRF 보호 누락 (cookie auth): SameSite=strict + CSRF token.
🤖 LLM 활용 힌트
인증 코드 작성 시 "access in memory + refresh in httpOnly cookie + rotation" 패턴 강제.