[Nest.js] Login Authentication 구현 ( passport + jwt + local strategy )
사용자인증은 대부분의 애플리케이션에서 필수적인 부분이다. 인증을 처리하는 방법은 다양하지만 Nest.js 공식홈페이지에서 passport를 안내하고 있어 필자는 passport와 jwt를 선택했다. jwt 전략을 사용 하기 전 passport-local을 먼저 구현하였다.
Nest.js로 Login 구현하기 (1)
패키지 설치 및 모듈 생성
$ npm install --save @nestjs/passport passport passport-local
$ npm install --save-dev @types/passport-local
패키지를 설치 후 모듈과 서비스를 생성한다.
$ nest g module auth
$ nest g service auth --no-spec
$ nest g module user
$ nest g service user --no-spec
passport local Strategy
// user/user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from 'src/entity/user.entity';
import { Repository } from 'typeorm';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
async findOne(id: string): Promise<User | undefined> {
return this.userRepository.findOne({
where: {
id: id,
}
});
}
}
// auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { UserService } from '../user/user.service';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(private userService: UserService) {}
async validateUser(id: string, password: string): Promise<any> {
const user = await this.userService.findOne(id);
if (!(await bcrypt.compare(password, user?.password ?? ''))) {
return null;
}
return user;
}
}
나는 typeorm
을 사용하여 데이터베이스에서 사용자 검색을 하였다. 데이터베이스에는 해시된 암호만 저장한 후 데이터를 비교하였다. bcrypt 라이브러리로 단방향해시 알고리즘을 사용하였다.
// auth/local.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({ usernameField: 'id' });
}
async validate(id: string, password: string): Promise<any> {
const user = await this.authService.validateUser(id, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
super()
을 호출하면서 옵션을 전달 할 수 있는데, default로 request는 username
과 password
속성이 필요하다. 다른 속성이름으로 지정하려면 super({usernameField: 'id'}
라고 옵션을 설정하면 된다.
// auth/guard/local-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
// app.controller.ts
import { Controller, Get, Post, UseGuards, Request } from '@nestjs/common';
import { AppService } from './app.service';
import { LocalAuthGuard } from './auth/guard/local-auth.guard';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@UseGuards(LocalAuthGuard)
@Post('auth/login')
async login(@Request() req) {
return req.user;
}
}
$ curl -X POST http://localhost:3000/auth/login -d '{"id": "nofunfromdev", "password": "test"}' -H "Content-Type: application/json"
validate()에서 반환되는 값은 자동으로 사용자 개체를 생성하여 req.user로 Request에 할당된다.
터미널에서 실행하면 정상적으로 사용자 값이 return 된다.
참고사이트
- 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 Progamming), FP (Functional Programming), and FRP (Functional Reac
docs.nestjs.com
