← 목록으로

JWT 리프레시 토큰 로테이션 구현기

2026년 2월 9일 · #nodejs #jwt

액세스 토큰 하나만 발급하고 만료 시간을 길게 잡아뒀었는데, 토큰이 탈취되면 만료 전까지 계속 악용될 수 있다는 게 마음에 걸려서 리프레시 토큰 방식으로 바꿨다.

기본 구조

액세스 토큰은 짧게(15분), 리프레시 토큰은 길게(2주) 발급하고, 액세스 토큰이 만료되면 리프레시 토큰으로 새 액세스 토큰을 발급받는 구조로 잡았다.

function issueTokens(userId) {
    const accessToken = jwt.sign({ id: userId }, ACCESS_SECRET, { expiresIn: "15m" });
    const refreshToken = jwt.sign({ id: userId }, REFRESH_SECRET, { expiresIn: "14d" });
    return { accessToken, refreshToken };
}

로테이션 + 탈취 감지

리프레시 토큰을 한 번 쓰면 즉시 폐기하고 새 리프레시 토큰을 발급하는 방식(로테이션)으로 바꿨다. 만약 이미 폐기된(사용된) 리프레시 토큰으로 다시 요청이 들어오면, 탈취된 것으로 간주하고 해당 사용자의 모든 세션을 강제로 끊도록 처리했다.

DB에 리프레시 토큰의 사용 여부를 기록해두는 테이블을 하나 추가해서, 매 요청마다 이미 사용된 토큰인지 대조하는 방식으로 구현했다.

느낀 점

구현 자체는 어렵지 않았는데, 클라이언트 쪽에서 토큰 갱신 타이밍을 놓치지 않게 처리하는 부분이 은근히 까다로웠다. 액세스 토큰 만료 직전에 미리 갱신 요청을 보내는 방식으로 마무리했다.