이전 글에서 Refresh token을 이용한 방식을 함께 살펴보기로 했었는데요!
JWT의 Refresh Token
Refresh 토큰이란?
JWT(JSON Web Token)는 유효 기간이 있으며, 만료되면 새로운 토큰을 발급받아야 합니다. 하지만 사용자가 자주 로그인하는 것은 불편하기 때문에, '리프레시 토큰(Refresh Token)'이 사용됩니다. JWT가 만료되면 클라이언트는 리프레시 토큰으로 새로운 JWT를 요청하여 사용자가 계속 서비스를 이용할 수 있게 합니다.
- Access Token의 유효기간은 짧다. (ex. 60일 - 마이크로소프트, 1시간 - 아마존)
- Refresh Token의 유효기간은 길다. (ex. 1년 - 마이크로소프트)
- 평소에 API 통신할 때는 Access Token을 사용하고, Refresh Token은 Access Token이 만료되어 갱신될 때만 사용합니다.
즉, 통신과정에서 탈취당할 위험이 큰 Access Token의 만료 기간을 짧게 두고 Refresh Token으로 주기적으로 재발급함으로써 피해을 최소화합니다.
서버-클라이언트 통신
1. 로그인 인증에 성공한 클라이언트는 Refresh Token과 Access Token 두 개를 서버로부터 받는다.
2. 클라이언트는 Refresh Token과 Access Token을 로컬에 저장해놓는다.
3. 클라이언트는 헤더에 Access Token을 넣고 API 통신을 한다. (Authorization)
4. 일정 기간이 지나 Access Token의 유효기간이 만료되었다.
4-1. Access Token은 이제 유효하지 않으므로 권한이 없는 사용자가 된다.
4-2. 클라이언트로부터 유효기간이 지난 Access Token을 받은 서버는 401 (Unauthorized) 에러 코드로 응답한다.
4-3. 401를 통해 클라이언트는 invalid_token (유효기간이 만료되었음)을 알 수 있다.
헤더에 Access Token 대신 Refresh Token을 넣어 API를 재요청한다.
Refresh Token으로 사용자의 권한을 확인한 서버는 응답쿼리 헤더에 새로운 Access Token을 넣어 응답한다.
만약 Refresh Token도 만료되었다면 서버는 동일하게 401 error code를 보내고, 클라이언트는 재로그인 해야합니다.
JWT Refresh Token 사용하여 추가 작성한 코드
//auth.js
const generateRefreshToken = (userId) => {
try {
const refreshToken = jwt.sign({ userId }, process.env.REFRESH_TOKEN_SECRET, {
expiresIn: '7d', // Refresh 토큰 유효 기간: 7일
});
return refreshToken;
} catch (err) {
console.error('Error generating refresh token:', err.message);
return null;
}
};
//userRouter.js
try {
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, {
expiresIn: "1h", // JWT 토큰 유효 기간: 1시간
});
const refreshToken = jwt.sign({ userId: user._id }, process.env.REFRESH_TOKEN_SECRET, {
expiresIn: "7d", // Refresh 토큰 유효 기간: 7일
});
res.status(200).json({ token, refreshToken, userId: user._id });
} catch (err) {
console.error("Error:", err.message);
console.error("Stack:", err.stack);
res.status(500).json({ message: "서버 오류" });
}
하지만 리프레시 토큰만으로는 완벽한 보안을 제공하지 않습니다.
만약 토큰이 탈취되었다면? 스택오버플로우 참고
- 데이터베이스에 각 사용자에 1대1로 매핑되는 Access Token, Refresh Token을 저장합니다.
- 정상적인 사용자는 기존의 Access Token으로 접근하며 서버측에서는 데이터베이스에 저장된 Access Token과 비교하여 검증합니다.
- 공격자는 탈취한 Refresh Token으로 새로 Access Token을 생성합니다. 그리고 서버측에 전송하면 서버는 데이터베이스에 저장된 Access Token과 공격자에게 받은 Access Token이 다른 것을 확인합니다.
- 만약 데이터베이스에 저장된 토큰이 아직 만료되지 않은 경우, 즉 굳이 Access Token을 새로 생성할 이유가 없는 경우 서버는 Refresh Token이 탈취당했다고 가정하고 두 토큰을 모두 만료시킵니다.
- 이 경우 정상적인 사용자는 자신의 토큰도 만료됐으니 다시 로그인해야 합니다. 하지만 공격자의 토큰 역시 만료됐기 때문에 공격자는 정상적인 사용자의 리소스에 접근할 수 없습니다.
공격자가 Refresh Token을 탈취하여 정상 사용자가 Access Token을 재발급받기 전에 자신이 먼저 Access Token을 생성한다면, 서버는 두 토큰 간의 충돌을 감지하게 됩니다. 이 경우 서버는 두 토큰을 모두 폐기해야 합니다. 이를 방지하기 위해 IETF 문서에서는 Refresh Token도 액세스 토큰과 동일한 유효 기간을 가지도록 하고, 사용자가 리프레시 토큰으로 액세스 토큰을 발급받을 때마다 Refresh Token 도 다시 발급받도록 권장하고 있습니다.
만약에! 공격자가 Access Token, Refresh Token을 둘 다 탈취한다면 어떻게 할까요? 이 때는 방법이 없고, 로직을 강화하여 토큰이 유출되지 않도록 보완하는 수 밖에 없습니다.
이를 설명할 때 다음과 같이 표현했습니다.
but then again there is nothing like 100% security.
'Framework > Node.js' 카테고리의 다른 글
[Node.js] Naver Oauth로 로그인 구현해보기 (0) | 2024.07.25 |
---|---|
[node.js] blocking vs nonblocking (2) | 2024.07.05 |
[node.js] JWT(Json Web Token)토큰 사용해보기 (2) | 2024.07.04 |