본문 바로가기

카테고리 없음

[Nest.js] 로컬 패스포트 with 가드

auth.controller.ts

 // 로그인
  @UseGuards(LocalAuthenticationGuard)
  @Post('/log-in')
  async login(
    @Body() loginUserDto: LoginUserDto,
    @Req() req: Request,
    @Res() res: Response,
  ) {
    const userData: any = req.user;
    const { accessToken, refreshToken } = await this.authService.login(
      userData,
      req.cookies,
    );

    res.cookie('accessToken', accessToken);
    res.cookie('refreshToken', refreshToken);
    res.send({ accessToken, refreshToken, userId: userData.id });
  }

먼저 로그인 요청이 들어오면 @UseGuards 데코레이터를 통해 입력된 가드를 먼저 거칩니다.

 

localAuthentication.guard.ts

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()							// local.strategy 로 보냅니다
export class LocalAuthenticationGuard extends AuthGuard('local') {}

local.strategy.ts

import {
  Req,
  Res,
  Body,
  CACHE_MANAGER,
  ConflictException,
  Inject,
  Injectable,
  NotFoundException,
  UnauthorizedException,
} from '@nestjs/common';
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { LoginUserDto } from './dtos/login-user.dto';
import { Request, Response } from 'express';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super({ usernameField: 'userId', passwordField: 'password' });
  }
  async validate(userId: string, password: string): Promise<any> {
    const userData = await this.authService.validateUser(userId, password);
    if (!userData) {
      throw new UnauthorizedException();
    }
    return userData;
  }
}

 

여기서 validate 메소드를 거치는데 validate 메소드는 입력된 정보와 맞는 유저 정보가 있는지 검증해서 내보내줍니다.

생성자에 super함수 안에는 nest에서 제공되는 키 값을 먼저 사용하고 뒤에 입력될 벨류는 자신에게 맞게 수정할 수 있습니다.

아래 링크는 네스트 공식 문서로 패스포트 관련 내용을 확인할 수 있습니다.

https://docs.nestjs.com/security/authentication

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea

docs.nestjs.com

여기서 내보내줄 데이터는 이제 authService의 validateUser 메소드에서 가져옵니다.

 

auth.service.ts

async validateUser(userId: string, password: string) {
    const userData = await this.getUserSelect({ userId }, ['id', 'password']);
    if (!userData) {
      throw new NotFoundException('아이디가 존재하지 않습니다.');
    }

    const isEqual = await bcrypt.compare(password, userData.password);
    if (isEqual === false) {
      throw new UnauthorizedException('비밀번호가 다릅니다.');
    }
    return userData;
  }

간단한 검증을 마치고 userData를 내보냅니다.

 

이제 위에서 받아온 userData는 req안에 있기 때문에 다시 변수에 담아서 사용합니다.

// 로그인
  @UseGuards(LocalAuthenticationGuard)
  @Post('/log-in')
  async login(
    @Body() loginUserDto: LoginUserDto,
    @Req() req: Request,
    @Res() res: Response,
  ) {
    const userData: any = req.user;
    const { accessToken, refreshToken } = await this.authService.login(
      userData,
      req.cookies,
    );

    res.cookie('accessToken', accessToken);
    res.cookie('refreshToken', refreshToken);
    res.send({ accessToken, refreshToken, userId: userData.id });
  }

 

auth.service.ts

async login(userData, { refreshToken: refreshTokenCookie = undefined }) {
    if (refreshTokenCookie) {
      await this.cacheManager.del(refreshTokenCookie);
    }

    const accessToken = await this.createAccessToken(userData.id);
    const refreshToken = await this.createRefreshToken();
    await this.cacheManager.set(refreshToken, userData.id);

    return { accessToken, refreshToken, userId: userData.id };
  }

서비스의 로그인 메소드를 거치면서 토큰을 캐시에 담고 필요한 데이터인 유저의 id번호를 보내줍니다.

 

컨트롤러로 돌아와서 쿠키에 토큰을 담아서 보내주고 필요했던 데이터도 보냅니다.

    res.cookie('accessToken', accessToken);
    res.cookie('refreshToken', refreshToken);
    res.send({ accessToken, refreshToken, userId: userData.id });

 

login.ejs

loginBtn.addEventListener('click', () => {
      try {
        axios
          .post('/api/auth/log-in', {
            userId: userId.value,
            password: password.value,
          })
          .then((response) => {
            localStorage.setItem('userId', response.data.userId);
            window.location.href = '/';
          })
          .catch((error) => {
            alert(error.response.data.message);
            password.value = '';
          });
      } catch (error) {
        console.error(error.message);
      }
    });

마지막으로 api를 요청했던 프론트로 userId 와 password를 보내주고 성공하면

userId를 로컬 스토리지에 담아주고 토큰도 발급받은 상태가 되었습니다.