[간병인 프로젝트 리팩토링] 로그아웃 (NestJS)

윤학·2023년 8월 8일
0

간병인 프로젝트

목록 보기
5/8
post-thumbnail

이제 계정 관련해서 마지막으로 로그아웃만 리팩토링 과정이 남았다.

로그아웃 로직은 그동안 만들어놨던 메서드들을 재사용하기도 하여 비교적 글이 간단할 것 같다.

로직설명

로그아웃 Api로 요청을 보내면 세션리스트에서 해당 아이디를 삭제한다.

기존 코드

기존 코드를 살펴보았더니 기존에는 백엔드에서 세션리스트를 따로 만들어놓지 않아 프론트엔드에서만 redux store에 저장하던 값들을 초기화하면서 로그아웃을 수행했다.

리팩토링 코드

auth.controller.ts

	/* 로그아웃 */
    @UseFilters(TokenExpiredExceptionFilter)
    @Post('logout')
    async logout(@AuthenticatedUser() user: User): Promise<void> {
        return await this.authService.logout(user)
    }

앞선 글들에서 살펴보았듯이 @Public() 데코레이터가 붙어있지 않은 router들은 전역으로 설정된 JwtGuard로 인증과정을 거친다.

그래서 인증된 사용자는 request의 user필드에 값이 저장되어 @AuthenticatedUser() 데코레이터로 가져온다.

auth.service.ts

	/* 로그아웃 -> 세션 리스트에서 사용자의 토큰 삭제 */
    async logout(user: User): Promise<void> {
        await this.sessionService.deleteUserFromList(user.getId());
    }

AuthService에서는 단순히 JwtToken의 Payload에 저장된 userId를 세션리스트에서 삭제하면서 로그아웃 로직을 마쳤다.

그럼 controller에서 왜 Filter를 적용했을까?

token-expired-exception.filter.ts

@Catch(UnauthorizedException)
export class TokenExpiredExceptionFilter implements ExceptionFilter {
  constructor(
    private readonly sessionService: SessionService,
    private readonly tokenService: TokenService
  ) { }

  async catch(exception: UnauthorizedException, host: ArgumentsHost) {
    const [request, response] = [
      host.switchToHttp().getRequest(),
      host.switchToHttp().getResponse()
    ];

    /* 만료된 토큰으로 인한 오류면 세션리스트에서 사용자 삭제 */
    if (exception.message === ErrorMessage.ExpiredToken)
      await this.deleteTokenFromSessionList(request);

    response.status(exception.getStatus())
            .send({ statusCode: exception.getStatus(), message: exception.message });
  }

  /* 세션리스트에서 해당 토큰 삭제 */
  private async deleteTokenFromSessionList(request: any) {
    const accessToken = this.tokenService.extractTokenFromHeader(request); // request로부터 토큰 추출
    const { userId } = this.tokenService.decode(accessToken); // 토큰의 payload에서 userId 추출
    await this.sessionService.deleteUserFromList(userId); // 해당 사용자 세션리스트에서 삭제
  };
}

해당 ExceptionFilter는 401에러 중 만료된 토큰으로 인한 오류에 대해서만 세션리스트에서 해당 사용자를 삭제하는 로직을 수행하는데,

만약 이 Filter 없이 로그아웃을 하는 사용자가 만료된 토큰을 가지고 있다면 전역으로 설정된 JwtGuard에서 막혀 계속해서 로그아웃을 못하는 상황이 발생할 것이라고 생각했다.

그렇다고 로그아웃을 할 사용자에 대해서 다시 RefreshToken으로 갱신하는 것 역시 불필요하다고 생각하여 Filter를 통해 세션리스트에서 삭제했다.

추가로 개선할 점...

위의 로직을 생각해보니 만약 토큰이 탈취당했을 경우 해당 토큰을 가지고 계속해서 로그아웃 Api로 보낸다면 실제 유효한 토큰을 가지고 활동하는 사용자는 인증이 필요한 Api에 요청을 보낼 때마다 이미 로그아웃이 되어 있을 것이다.

토큰을 검사하는 로직이 있어야 할텐데 추후에 개선을 해보자.

profile
해결한 문제는 그때 기록하자

0개의 댓글