NestJS에서 JWT 인증 흐름 정리 (Login ~ Logout)

여리·2025년 7월 12일

혼자서 다시 로그인에 대해서 복기해보면서 스터디를 하고자
NestJS로 인증 시스템을 구현하며 accessToken과 refreshToken을 사용하는 전형적인 JWT 인증 구조를 정리해봤다.

service의 구조영역은 따로표현하지 않고 controller기준까지로만 해당 내용을 작성해보았다.


🔐 인증 흐름 개요

1. 로그인

  • 사용자 인증 후 JWT 발급
  • accessToken: 15분 유효 / Authorization 헤더에 저장
    • accessToken은 토큰이 다른 사람에게 탈취되는 경우를 대비해서 짧은 시간으로 갖도록 한다.
  • refreshToken: 7일 유효 / HttpOnly 쿠키에 저장
    • refreshToken은 accessToken을 갖고 있는 상황에서 쿠키를 통해 특이사항이 없으면 accessToken이 만료되면 다시 accessToken을 새로 발급받아 지속적으로 서비스를 이용할 수 있도록 하고자 함.

2. 인증 유지

  • accessToken 만료 시 → refreshToken으로 /auth/refresh API 호출
  • 새 토큰 발급 후 다시 인증 요청 가능

3. 로그아웃

  • 서버에서 refreshToken 쿠키 제거 (clearCookie)
  • 클라이언트는 Authorization 헤더에서 accessToken 제거

📥 로그인 API

🔗 POST /auth/login

controller에서 아래와 같이 처리하였다.
return의 user 부분은 프론트에서 유저의 정보를 사용한다는 가정에서 추가한 값이다.

@Post('login')
async login(
  @Body() loginDto: LoginDTO,
  @Res({ passthrough: true }) res: Response,
) {
  const loginInfo = await this.authService.login(loginDto);

  res.header('Access-Control-Expose-Headers', 'Authorization');
  res.setHeader('Authorization', 'Bearer ' + loginInfo.accessToken);
  res.cookie('refreshToken', loginInfo.refreshToken, {
    httpOnly: true,
    maxAge: 1000 * 60 * 60 * 24 * 7, // 7일
  });

  return {
    message: 'success',
    user: loginInfo.payload,
  };
}

🔁 토큰 재발급 API

🔗 POST /auth/refresh

@Post('refresh')
async refresh(@Req() req: Request, @Res({ passthrough: true }) res: Response) {
  const refreshToken = req.cookies.refreshToken;
  if (!refreshToken) throw new UnauthorizedException('Refresh token not found');

  const { accessToken, refreshToken: newRefreshToken } =
    await this.authService.refreshAccessToken(refreshToken);

  res.setHeader('Authorization', 'Bearer ' + accessToken);
  res.cookie('refreshToken', newRefreshToken, {
    httpOnly: true,
    maxAge: 1000 * 60 * 60 * 24 * 7,
  });
}

👤 내 정보 가져오기 API

🔗 GET /auth/me

@Get('me')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('accessToken')
getMe(@Req() req: Request) {
  return req.user;
}

이번에는 스웨거를 사용였다.
Swagger에서 테스트 시 Authorize 버튼 클릭 → Bearer <accessToken> 입력 필요


🚪 로그아웃 API

🔗 POST /auth/logout

이번의 케이스는 accessToken은 reqeust의 headers의 Authorization에 있기때문에 해당 부분은 클라이언트(프론트)에서 핸들링 하여야 한다
그래서 로그아웃 경우에는 refreshToken의 쿠키만 제거해주는것이 api에서의 역할이다.

@Post('logout')
logout(@Res({ passthrough: true }) res: Response): ResponseCommonSuccessDTO {
  res.clearCookie('refreshToken');
  return {
    message: 'logout',
    statusCode: HttpStatus.OK,
  };
}

🛡 JwtStrategy 설정

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.JWT_SECRET_KEY || 'jwtSecretKey',
      ignoreExpiration: false,
    });
  }

  async validate(payload: Payload) {
    return {
      id: payload.id,
      userUuid: payload.userUuid,
      name: payload.name,
      email: payload.email,
    };
  }
}

📋 Swagger에서 headers의 Authorization 설정

📌 main.ts

.addBearerAuth(
  {
    type: 'http',
    scheme: 'bearer',
    bearerFormat: 'JWT',
    name: 'Authorization',
    in: 'header',
  },
  'accessToken',
)

@ApiBearerAuth('accessToken')를 사용하는 API에서 Swagger UI의 Authorize 버튼으로 토큰 입력 가능


🧠 정리

항목저장 위치설명
accessTokenAuthorization 헤더클라이언트에서 인증 요청 시 사용
refreshTokenHttpOnly 쿠키토큰 만료 시 재발급 용도
getMeJWT 유효성 검사JwtAuthGuard를 통해 검증
logout쿠키 제거서버 측에서 refreshToken 쿠키 삭제

🗂 앞으로 고려할 수 있는 것들

  • refreshToken DB 저장 및 블랙리스트 처리
  • OAuth2 / Social 로그인 (카카오 등)
  • 관리자 권한 인증을 위한 RoleGuard
  • Swagger 환경에서 자동 토큰 입력 처리 등
profile
beckend developer

0개의 댓글