Nest.js를 배워보자/7. NestJS 인증 시스템 구축 — JWT & Passport 실전

WT 인증 시스템 구축: Refresh Token 패턴

_Blue_Sky_ 2025. 12. 2. 20:06
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 패턴의 작동 흐름

  1. 로그인 (POST /auth/login):
    • 서버는 Access TokenRefresh Token 두 개를 발급합니다.
    • Refresh Token은 데이터베이스에 해시 처리 후 저장됩니다.
    • 두 토큰을 클라이언트에 반환합니다.
  2. API 요청:
    • 클라이언트는 Access Token을 요청 헤더에 담아 API를 호출합니다.
  3. 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