Stouter
Stouter

백엔드 개발자입니다.

인증과 인가를 위한 Nestjs의 Guard

NestJS에서는 인증 부분을 Guard를 통해 쉽게 구현할 수 있다. 우리는 현재 두 가지의 Guard를 사용하고 있다. 두 가지의 Guard의 이름을 criticalGuardlooseGuard로 지었다.

CriticalGuard

  • 우리의 핵심 서비스를 감싸는 Guard로써 쿠키의 accessToken을 확인한다.
  • 게임 입장, 게시글 작성, 유저 API 호출 등 다양한 서비스에 사용된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// jwt-auth.strategy.ts
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, "criticalGuard") {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromExtractors([
        (request) => {
          return request?.cookies?.accessToken;
        },
      ]),
      ignoreExpiration: false,
      secretOrKey: process.env.JWT_SECRET_KEY,
    });
  }

  async validate(payload: UserDataDto): Promise<UserDataDto> {
    if (!payload.nickname) {
      throw new UnauthorizedException(
        "서버에 해당 유저가 존재하지 않습니다. 가입을 완료해주세요."
      );
    }
    return payload;
  }
}
  • criticalGuard는 cookie에 accessToken 이라는 토큰이 있는 지를 확인하고, 해당 토큰의 payload 부분을 가공하여 validate 시킨다.
  • payload 의 nickname 부분을 확인하여 nickname 이 정해지지 않았다면, 해당 유저는 회원가입을 완료하지 않은 유저이다. 우리는 nickname 필드의 값 유무를 통해 앞선 케이스를 구분했다.

looseGuard

  • 오직 signupAPI 를 위해 만든 guard로 accessToken 유무만 확인한다.
  • 만약 우리의 jwt로 검증이되는 accessToken이라면, 해당 유저는 통과한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// jwt-auth.strategy.ts
@Injectable()
export class JwtStrategy2 extends PassportStrategy(Strategy, "looseGuard") {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromExtractors([
        (request) => {
          return request?.cookies?.accessToken;
        },
      ]),
      ignoreExpiration: false,
      secretOrKey: process.env.JWT_SECRET_KEY,
    });
  }

  async validate(payload: UserDataDto): Promise<UserDataDto> {
    return payload;
  }
}


인증

로그인과 회원가입 로직을 완성했으니, 본격적인 인증 API를 만들어보자. 우리는 이를 auth API라고 부른다. 앞에서 guard를 잘 만들어 놓았기에 별 다른 복잡한 코드가 필요하지 않았다.

Auth

1
2
3
4
5
6
@Get('auth')
  @UseGuards(AuthGuard('criticalGuard'))
  authorization(@Req() req: any) {
    const { nickname, characterName }: UserDataDto = req.user;
    return { nickname, characterName };
 }
  • 유저가 우리 서비스의 중요한 페이지를 들어갈 때마다, Frontend에서는 해당 API를 호출하여 접근 허가를 결정한다.
  • criticalGuard를 통해 가입 유저인지를 확인하고, nicknamecharacterName을 반환해준다.


로그인 총 과정 순서도

Untitled