안녕하세요 이번 주제는, 로그인을 위한 passport-local을 nestjs상에 붙이는 작업에 대한 이야기를 다루어볼까 합니다.
이번 포스트에서 삽질한 부분은
🧗♀️
@UseGuards(AuthGuard('local'))
에 따른 적용문제였습니다.
nestjs문서 - passport관련 부분을 볼 수 있습니다.(참고:https://docs.nestjs.com/security/authentication)
nestjs안에서 passport-local을 사용하기위해선 몇가지의 절차가 필요합니다.
1단계,
$ nest g module auth
$ nest g service auth
를 통해서 해당하는 파일들을 만들어냅니다.
만들어진 파일들에 대해 소개하겠습니다.
auth.module.ts
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Users } from 'src/entities/Users.entity';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';
@Module({
imports: [PassportModule],
providers: [AuthService, LocalStrategy],
})
export class AuthModule {}
와 같이 생겼습니다.
auth.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Users } from 'src/entities/Users.entity';
import { Repository } from 'typeorm';
import bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
@InjectRepository(Users) private userRepository: Repository<Users>,
) {}
async validateUser(email: string, password: string): Promise<any> {
const user = await this.userRepository.findOne({
where: { email },
select: ['email', 'password', 'nickname'],
});
if (!user) {
return null;
}
const result = await bcrypt.compare(password, user.password);
console.log('reuslt: ', result);
if (result) {
const { password, ...userWithoutPassword } = user;
return userWithoutPassword;
}
return null;
}
}
와같이 생성되어있습니다.
이렇게 두가지에 대한 파일을 정리하였고
nestjs공식문서에서 만들라고 제시해준 local.strategy파일을 밑에와 같이 생성했습니다.
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();
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
해당 파일을 생성하여 해당 코드를 입력하면 passportStrategy를 사용할 수 있는 준비가 완료됩니다.
그리고 나서 이제 passport를 사용하기만 하면됩니다.
import { Controller, Request, Post, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Controller()
export class AppController {
@UseGuards(AuthGuard('local'))
@Post('auth/login')
async login(@Request() req) {
return req.user;
}
}
위의 코드와 같이 사용하면 됩니다. 그러나 저에겐 하나의 물음과 문제 존재했습니다.
🤔하나의 물음:
어떻게
@UseGuards(AuthGuard('local'))
만을 작성해준 것 만으로도, authGuard가 작동하는 것일까? 당연히 nestjs에서 제공하는 데코레이션 기능을 사용하기때문이지만
AuthGuard('local')를 어떻게 인식할 수 있을까?에대한 고민이 들었습니다.
그 해답은
@UseGuards(AuthGuard('local'))를 입력해주면 nestjs의 @UseGuards에 ()안에 작성된 AuthGuards는 @nestjs/passport에서 가져온 정보이고, nestjs에서 local.strategy.ts파일 안에
export class LocalStrategy extends PassportStrategy(Strategy)
로 선언된 것을 확인하고 해당 코드를 불러서 사용하는 것입니다.
🤔하나의 문제:
왜 코드를 완성했음에도 불과하고 인증에러가 났을까?
그이유는
http://www.passportjs.org/docs/username-password/
에서 찾아볼 수 있었습니다.
기본적으로 및 LocalStrategy라는 매개변수에서 자격 증명을 찾을 것으로 예상합니다 . 사이트에서 이러한 필드의 이름을 다르게 지정하려는 경우 옵션을 사용하여 기본값을 변경할 수 있습니다.username password로 되어있다는 것입니다.
따라서 저의 경우 username-> email을 매개변수로 사용해야하기때문에 바꿔주어야했습니다.
local.strategy.ts
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({ <- PassportStrategy의 객체의 매개변수를 아래와 같이 변경해야합니다.
usernameField: 'email',
password: 'password',
});
}