리프레시 토큰 로직은 크게
(1) 토큰 조회/검증과 (2) 재발급(회전, rotation)으로 동작
1) 조회(검증) 로직
요청 엔드포인트: POST /auth/refresh (api/routes/api.php)
컨트롤러 AuthController::refresh()가 먼저 쿠키에서 토큰을 꺼냅니다.
$_COOKIE[CookieHelper::getRefreshCookieName()] (기본 이름 refreshToken)
서비스 AuthService::refresh()에서:
토큰 누락이면 AUTH_REFRESH_MISSING (401)
RefreshTokenStore::validateAndGetUser() 호출
토큰 원문을 SHA-256 해시
refresh_tokens.token_hash로 DB 조회 + users 조인
상태 판정
레코드 없음/만료: 유효하지 않음
revoked_at 있음: 재사용(reuse) 탐지
정상: 사용자 정보 반환 + last_used_at 갱신
2) 재발급(회전) 로직
AuthService::refresh()에서 검증 통과 후 RefreshTokenStore::rotate() 실행
rotate() 핵심 순서:
트랜잭션 시작
기존 refresh 토큰 row를 FOR UPDATE로 잠금 조회
기존 토큰이 revoked/expired면 실패
새 refresh 토큰 생성 후 DB insert
기존 토큰 revoked_at = NOW()로 폐기
커밋
이후 새 Access Token(JWT) 발급
쿠키 갱신:
새 refresh 쿠키 설정
새 access 쿠키 설정
응답 바디에 access_token, expires_in 반환
3) 재사용 공격 방어
이미 폐기된 refresh 토큰이 다시 들어오면 reused=true
AuthService::refresh()에서:
기본 정책(REUSE_REVOKE_SCOPE)에 따라
device: 같은 user_id + device_id 활성 토큰 revoke
user: 해당 사용자 전체 revoke
인증 쿠키 삭제 후 401 AUTH_REUSE
4) 조회를 세션 목록 조회로 본다면
GET /auth/sessions (AuthController::sessions())
refresh_tokens에서 revoked_at IS NULL AND expires_at NOW()만 조회
현재 쿠키 refresh 토큰 해시와 각 row의 token_hash를 비교해 is_current 표시