일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Queue
- typeORM
- nodejs
- MongoDB
- dfs
- Nest.js
- game
- Sequelize
- 게임
- AWS
- Bull
- 자료구조
- nestjs
- jest
- Dinosaur
- react
- 공룡게임
- cookie
- Python
- Express
- OCR
- 정렬
- TypeScript
- mongoose
- class
- MySQL
- flask
- JavaScript
- GIT
- Today
- Total
포시코딩
[Nest.js] passport, jwt를 통한 로그인 구현(1) - passport-local 본문
개요
나는 이전까지 middleware를 통해 jwt를 통한 로그인 기능을 직접 구현하기만 해봤던지라
이전 프로젝트에서 passport, nest.js의 guard를 활용해 로그인 기능을 구현한 팀원의 코드를 이해하고자
직접 코드를 작성해보며 정리해봤다.
그 과정에서 참고한 내용 출처는 다음과 같다.
이전 프로젝트에서 로그인 기능을 담당해준 팀원의 코드
https://github.com/chalkak2023/Chalkak-Backend
좀 더 이해를 쉽게 하기 위해 오로지 nest.js에서의 passport 사용 방법에 대해 포스팅한 글
https://velog.io/@junsugi/Nest.js-로그인-서비스-만들어보기-2
passport-local을 통한 로그인
설치
npm i @nestjs/passport passport-local
npm i -D @types/passport-local
시작 전
일단 첫번째로 Nest.js에서의 passport 동작 원리를 이해하는게 목적이라
Guard를 통해 커스텀 strategy를 통과 시키고
커스텀 decorator를 만들어 통과되며 받아지는 req.user 값을 확인하는 과정을 확인해볼 생각이다.
참고로 타입은 당장 중요한게 아니라서 any로 진행되는게 많을거임.
📦auth
┣ 📜auth.controller.ts
┗ 📜auth.module.ts
먼저 nest 명령어를 통해 auth module과 controller를 만들어줬다.
auth.controller.ts
@Controller('auth')
export class AuthController {
@Post()
async login(@Body() body: any) {
console.log(`body: `);
console.dir(body);
return;
}
}
controller도 일단 데이터를 받을 수 있게 세팅해줬으며
Insomnia를 통해 api 요청 시 정상적으로 데이터가 전달되었다.
Custom Strategy 세팅
Custom strategy 세팅에 들어가기 앞서 strategy란?
'전략'이란 뜻으로
Guard가 요청을 받았을 때, 해당 요청에 대한 인증을 수행한다면
Strategy는 Guard 내에서 인증을 수행하기 위해 사용되며, 인증 결과를 Guard에 반환하는 역할이라고 볼 수 있다.
local.strategy.ts
import { Injectable } from "@nestjs/common";
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
constructor() {
super();
}
async validate(email: string, password: string) {
console.log('in LocalSrategy');
console.log(`email: ${email}, password: ${password}`)
return 'local.strategy - validate return value';
}
}
일단 로그인 할 때는 jwt가 아닌 유저 id와 password를 받아 인증을 거쳐야 하기 때문에
passport-local을 써야한다.
extends PassportStrategy(Strategy, 'local') 부분을 보면
Strategy는 passport-local에서 import해 가져오는걸 확인할 수 있고
'local'이라는 이름으로 만들고 있다.
이렇게 들어온 값에 대해서 validate 메서드에서 파라미터로 받아
내부 로직을 거친 후 return 하게 되면 req.user에 세팅되게 된다.
여기서 나는 일부러 파라미터 이름을 email, password로 사용했는데
알아야 할게 passport-local은 username과 password만 받을 수 있다.
때문에 위와 같이 username이 아닌 email로 데이터를 보내면 401 Unauthorized 실패를 반환 받는다.
그럼 왜 validate 메서드에선 에러가 안났냐?
validate메서드로 들어올 때, username과 password는 각각 첫번째, 두번째 파라미터로 전달 받는다.
때문에 원래의 이름은 상관없어 지는 것
그래도 클라이언트로 하여금 email을 전달해야 하는데 username을 프로퍼티로 하면 헷갈릴 수 있으니
아래와 같이 세팅해줄 수 있다.
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
constructor() {
super({ usernameField: 'email' });
}
async validate(email: string, password: string) {
console.log('in LocalSrategy');
console.log(`email: ${email}, password: ${password}`)
return 'local.strategy - validate return value';
}
}
super에서 usernameField로 'email'이라 지정 시
email로 보내야 괜찮고 오히려 username이라고 보낼 경우 401을 반환하는 것을 확인할 수 있다.
Guard 세팅
자, 그래서 이 strategy를 어떻게 사용할 수 있느냐.
auth.controller.ts
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ReqUser } from './auth.decorator';
@Controller('auth')
export class AuthController {
@Post()
@UseGuards(AuthGuard('local'))
async login(@Body() body: any) {
console.log(`controller: `);
console.dir(body);
return;
}
}
@UseGuard 데코레이터를 통해 AuthGuard('local')을 세팅한다.
passport의 AuthGuard()를 통해 아까 세팅한 strategy의 이름인 'local'을 입력해주면 된다.
참고로 의존성 주입을 위해 module에 LocalStrategy가 povider로 등록되어 있어야 한다.
이렇게 세팅 후 아까와 똑같이 API를 호출하면
strategy와 controller에서 적어놓은 console을 통해 데이터가 잘 전달되고 있는 것을 확인할 수 있다.
정리
전달되는 body의 email, password는 strategy에서 처리되야 하므로
controller에서 직접 받을 필요가 없으니 제거했다.
추가로 strategy의 validate에서 return 하여
req.user에 세팅되는 'local.strategy - validate return value'라는 결과 값을
controller에서 받아야 하는데 커스텀 데코레이터를 사용하면 쉽게 받아올 수 있으니
커스텀 데코레이터를 만들어보자.
https://docs.nestjs.com/custom-decorators
나는 그냥 공식사이트에서 제공하는 커스텀 데코레이터 만드는 방법을 응용했고
마침 request.user 값을 빼내 리턴하는 코드가 그대로 있어 그냥 가져와 이름만 바꿔 사용했다.
auth.decorator.ts
import { ExecutionContext, createParamDecorator } from "@nestjs/common";
export const ReqUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
auth.controller.ts
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ReqUser } from './auth.decorator';
@Controller('auth')
export class AuthController {
@Post()
@UseGuards(AuthGuard('local'))
async login(@ReqUser() user: any) {
console.log(`ReqUser: `);
console.dir(user);
return;
}
}
@ReqUser()를 통해 user에 req.user로 전달되었던 strategy의 리턴 값이 들어와 출력되는 것을 확인할 수 있다.
나머진 간단하다.
Service에 로그인 여부를 확인하는 메서드를 만들어
return 'local.strategy - validate return value' 대신 실행되게 한 뒤
로그인 처리를 진행하면 된다.
마치며
이렇게 Guard, passport-local을 사용해 controller 진입 전에 생기는 흐름을 확인해봤는데
다음 포스팅에선 Service를 만들어 전달 받은 값들로 로그인 검증 후 jwt까지 생성하는 과정까지 진행해보려고 한다.
'Node.js' 카테고리의 다른 글
[NestJS] 다중 서버에서의 Bull, Event-Emitter - 해결중 (2) | 2023.07.17 |
---|---|
[Nest.js] applyDecorators - 작성중 (0) | 2023.04.06 |
4월3일 - Nest.js의 Class와 Factory (0) | 2023.04.04 |
[Nest.js] Gateway와 socket.io (0) | 2023.04.02 |
[Nest.js] 모임 참여 - 동시성 문제 해결 과정 정리 with. Bull & Event-Emitter (0) | 2023.04.02 |