[NestJS] 로그인 성공시 JWT 토큰 전달

jm4293·2024년 1월 2일
0

회원가입 및 로그인 구현

  • user.controller.ts
/* NestJS 백엔드 */
/* user.controller.ts */
@Controller('user')
export class UserController {
  PASSWORD_SALT = 10;
  constructor(private readonly userService: UserService) {}

  @Get('/login')
  login(@Req() loginUser: { query: LoginUserDto }) {
    return this.userService.login(loginUser.query);
  }

  @Post('/create')
  async createUser(@Body() input: CreateUserDto) {
    return this.userService.createUser(input);
  }
}
  • user.service.ts
/* NestJS 백엔드 */
/* user.service.ts */
@Injectable()
export class UserService {
  PASSWORD_SALT = 10;

  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  async login(input: LoginUserDto) {
    const query = `SELECT * FROM user WHERE email = '${input.email}'`;
    const validUser = await this.userRepository.query(query);
    const validPassword = await bcrypt.compare(input.password, validUser[0].password);

    if (validUser.length === 0) {
      throw new HttpException('존재하지 않는 이메일입니다.', HttpStatus.BAD_REQUEST);
    }

    if (!validPassword) {
      throw new HttpException('비밀번호가 일치하지 않습니다.', HttpStatus.BAD_REQUEST);
    }

    return {
      status: 200,
      message: '로그인이 완료되었습니다.',
      result: {
        email: validUser[0].email,
        name: validUser[0].name,
        age: validUser[0].age,
        phone: validUser[0].phone,
      },
    };
  }

  async createUser(input: CreateUserDto) {
    const email = input.email;
    const hashedPassword = await bcrypt.hash(input.password, this.PASSWORD_SALT);
    // const user = await this.userRepository.findOne({ where: { email } });

    const query = `SELECT * FROM user WHERE email = '${email}'`;
    const user = await this.userRepository.query(query);

    console.log('user', user);

    if (user.length !== 0) {
      throw new HttpException('이미 등록된 이메일입니다.', HttpStatus.BAD_REQUEST);
    }

    // const result = await this.userRepository.save({
    //   ...input,
    //   password: hashedPassword,
    // });

    const query2 = `INSERT INTO user (email, password, name, age, phone) VALUES (?, ?, ?, ?, ?)`;
    const values = [email, hashedPassword, input.name, input.age, input.phone];
    await this.userRepository.query(query2, values);

    return {
      status: 200,
      message: '회원가입이 완료되었습니다.',
    };
  }
}
  • 프론트엔드
/* React 프론트엔드 */
export const loginJWT_api = async (data: FormInputs1) =>
  await axios.post("http://localhost:8080/auth/login", data).then(res => res.data);

export const userCreate_api = async (data: FormInputs2) =>
  await axios.post("http://localhost:8080/user/create", data).then(res => res.data);

const login = useMutation({
  mutationKey: ["login"],
  mutationFn: (data: FormInputs1) => loginJWT_api(data),
  onSuccess: () => {
    alert("로그인이 완료되었습니다.");
    navigate("/board");
    reset1();
  },
  onError: (err: any) => {
    alert(err.response.data.message);
  },
});

const create = useMutation({
  mutationKey: ["userCreate"],
  mutationFn: (data: FormInputs2) => userCreate_api(data),
  onSuccess: () => {
    alert("회원가입이 완료되었습니다.");
    setIsRegister(false);
    reset2();
  },
  onError: (err: any) => {
    alert(err.response.data.message);
  },
});
  • 이번에도 ORM을 사용하지말고 SQL문으로 작성
  • 위 코드는 JWT 관련된 내용은 없고 회원가입과 로그인만 가능한 코드이다.
  • 저장 및 값 확인만 하는 내용이라 간단한 코드이다.

JWT 적용, accessToken, refreshToken 생성

  • auth.module.ts
/* NestJS 백엔드 */
/* auth.module.ts */
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { UserService } from './user.service';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';

@Module({
  imports: [JwtModule.register({}), TypeOrmModule.forFeature([User])],
  controllers: [AuthController],
  providers: [AuthService, UserService],
})
export class AuthModule {}
  • auth.controller.ts
/* NestJS 백엔드 */
/* auth.controller.ts */
@Controller('auth')
export class AuthController {
  constructor(
    private readonly userService: UserService,
    private readonly authService: AuthService,
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  @Post('login')
  async login(@Body() input: LoginUserDto, @Res() res: Response) {
    const { email, password } = input;

    const query = `SELECT * FROM user WHERE email = '${email}'`;
    const user = await this.userRepository.query(query);

    if (user.length === 0) {
      throw new HttpException('이메일 또는 비밀번호가 일치하지 않습니다.', HttpStatus.BAD_REQUEST);
    }

    const validPassword = await bcrypt.compare(password, user[0].password);

    if (!validPassword) {
      throw new HttpException('이메일 또는 비밀번호가 일치하지 않습니다.', HttpStatus.BAD_REQUEST);
    }

    this.authService.setRefreshToken({ user: user[0], res });

    const jwt = this.authService.getAccessToken({ user: user[0] });

    // return res.status(200).send(jwt);

    return res.status(200).json({
      status: 200,
      message: '로그인이 완료되었습니다.',
      result: {
        email: user[0].email,
        name: user[0].name,
        age: user[0].age,
        phone: user[0].phone,
        jwt,
      },
    });
  }
}
  • auth.service.ts
/* NestJS 백엔드 */
/* auth.service.ts */
@Injectable()
export class AuthService {
  constructor(private readonly jwtService: JwtService) {}

  getAccessToken({ user }): string {
    return this.jwtService.sign(
      {
        email: user.email,
      },
      {
        secret: process.env.ACCESS_TOKEN_SECRET_KEY,
        expiresIn: '5m',
      },
    );
  }

  setRefreshToken({ user, res }) {
    const refreshToken = this.jwtService.sign(
      {
        email: user.email,
      },
      {
        secret: process.env.ACCESS_TOKEN_SECRET_KEY,
        expiresIn: '2w',
      },
    );

    res.setHeader('Set-Cookie', `refreshToken=${refreshToken}`);

    // res.cookie('refreshToken', refreshToken, {
    //   httpOnly: true,
    //   path: '/',
    //   maxAge: 60 * 60 * 24 * 14 * 1000, // 2주
    //   // secure: true, // HTTPS 사용 시 주석 해제
    //   // domain: 'your-domain.com', // 필요한 경우 도메인 설정
    // });
  }
}
  • auth module 에서 JWT를 사용하기위해 선언
  • user controller가 아니라 auth controller로 변경
  • userRepository는 SQL문을 사용하기 위해서 일단 선언
  1. 사용자의 이메일과 비밀번호를 일단 validation 처리
  2. validation 통과가 된다면 setRefreshToken service로 user 데이터를 전달
  3. setRefreshToken를 실행하여 jwt토큰을 생성하는데 expire 기간을 2주로 설정
  4. response에 set-Cookie 이름으로 토큰을 저장하여 전달
    • 로그인 성공하면 http 응답헤더에 포함되어 받게된다.
  5. 다시 accessToken를 실행하여 expire 기간을 5분으로 설정
  6. accessToken은 return으로 전달하여 const jwt에 저장한다
  7. 로그인 성공하면 result 객체에 함께 accessToken을 전달한다.

다음 일정

  • guard와 passport를 이용하여 좀 더 보안적으로 적용예정.
profile
무언가를 만드는 것을 좋아합니다

0개의 댓글

관련 채용 정보