728x90

1. 🔑 Refresh Token 패턴의 개요
Refresh Token 패턴은 기존의 단일 Access Token 방식이 가진 보안과 사용자 경험(UX) 사이의 딜레마를 해결하기 위해 고안된 이중 토큰(Dual Token) 방식입니다.
- Access Token (액세스 토큰): 실제 API 접근 권한을 증명하는 토큰입니다. 보안을 위해 만료 시간을 짧게 (예: 15분) 설정하여 탈취 위험에 대비합니다.
- Refresh Token (리프레시 토큰): Access Token이 만료되었을 때 새 Access Token을 재발급받기 위한 토큰입니다. 사용자 경험을 위해 만료 시간을 길게 (예: 7일) 설정합니다.
728x90
2. 🛡️ 보안 강화 및 통제력 확보
| 토큰 종류 | 역할 | 만료 시간 | 저장 위치 (클라이언트) | 보안 전략 |
| Access Token | API 자원 접근 | 짧음 | 메모리 또는 로컬 저장소 | 탈취되어도 즉시 무력화 |
| Refresh Token | Access Token 재발급 | 김 | HttpOnly Cookie 또는 DB 관리 | 서버 측에서 즉시 무효화 가능 |
Refresh Token을 DB에서 관리하는 것이 핵심 보안 전략입니다. 사용자가 로그아웃하거나 의심스러운 활동이 감지되면 서버는 해당 Refresh Token을 DB에서 삭제하여 즉시 모든 세션을 무효화할 수 있습니다.
3. 🔄 Refresh Token 패턴의 작동 흐름
- 로그인 (POST /auth/login):
- 서버는 Access Token과 Refresh Token 두 개를 발급합니다.
- Refresh Token은 데이터베이스에 해시 처리 후 저장됩니다.
- 두 토큰을 클라이언트에 반환합니다.
- API 요청:
- 클라이언트는 Access Token을 요청 헤더에 담아 API를 호출합니다.
- Access Token 만료 시 (POST /auth/refresh):
- Access Token이 만료되어 API 요청에 실패하면, 클라이언트는 Refresh Token을 서버로 전송합니다.
- 서버는 Refresh Token Guard (JwtRefreshStrategy)를 통해 다음을 검증합니다.
- 토큰의 서명 및 만료 시간 검사.
- 토큰의 Payload (사용자 ID)를 이용하여 DB에 저장된 Refresh Token과 일치하는지 확인합니다.
- 모든 검증이 성공하면, 서버는 새 Access Token과 새 Refresh Token (선택적)을 발급하여 반환합니다.
4. 🛠️ NestJS 구현 요소 (Service 및 Guard)
4.1. AuthService (토큰 생성 및 DB 관리)
Access Token과 Refresh Token을 생성하고, Refresh Token을 DB에 저장/업데이트하는 로직을 구현합니다.
// auth.service.ts (주요 로직)
async login(user: any) {
const payload = { email: user.email, sub: user.id };
const accessToken = this.jwtService.sign(payload, { expiresIn: '15m' });
const refreshToken = this.jwtService.sign(payload, { expiresIn: '7d' });
// 💡 DB에 Refresh Token 저장 (보안을 위해 해시 처리)
await this.prisma.user.update({
where: { id: user.id },
data: { hashedRefreshToken: await hash(refreshToken) },
});
return { accessToken, refreshToken };
}
728x90
4.2. JwtRefreshStrategy와 Guard (토큰 재발급)
Refresh Token의 유효성 검증은 전용 Strategy를 통해 수행하며, 이 Strategy에서 DB에 저장된 해시 값과 비교하는 로직이 포함됩니다.
// jwt-refresh.strategy.ts (주요 validate 로직)
async validate(req: Request, payload: any) {
const refreshToken = req.cookies['refreshToken']; // HttpOnly Cookie에서 추출 가정
const user = await this.userService.getUserIfRefreshTokenMatches(
payload.sub, // 사용자 ID
refreshToken,
);
if (!user) {
throw new UnauthorizedException('유효하지 않은 리프레시 토큰입니다.');
}
// 💡 최종적으로 user 객체를 반환하여 req.user에 삽입
return user;
}
4.3. AuthController (토큰 재발급 라우트)
// auth.controller.ts (재발급 엔드포인트)
@Post('refresh')
@UseGuards(AuthGuard('jwt-refresh'))
async refreshTokens(@Request() req) {
// Guard를 통과했다는 것은 Refresh Token이 유효함을 의미
// req.user에는 사용자 정보가 담겨 있음
return this.authService.reissueTokens(req.user);
}
728x90
'Nest.js를 배워보자 > 7. NestJS 인증 시스템 구축 — JWT & Passport 실전' 카테고리의 다른 글
| Role 기반 권한 관리 (Role-Based Access Control, RBAC) (0) | 2025.12.02 |
|---|---|
| Passport Strategy 구현: Local & JWT 실전 (0) | 2025.12.02 |
| NestJS 인증 시스템 구축: JWT & Passport 실전 (0) | 2025.12.02 |