TIL

24/01/03 TIL __ nest js에서 passport (local)

GABOJOK 2024. 1. 3. 18:26

 

 

 express 환경에서 passport를 활용해서 local전략과, kakao전략을 사용한 경험이 있다. 

그런데 nest 에서는 어떻게 사용해야 할까? 

 

nest에서는 약간 사용법이 다르다. 

 

 

먼저 express에서 사용한 코드를 살펴보자

express에서 passport local

import passport from 'passport';
import passportLocal from 'passport-local';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import redis from 'redis';
const redisClient = redis.createClient();

redisClient.connect();
import { prisma } from '../utils/prisma/index.js';

const LocalStrategy = passportLocal.Strategy;

export default () => {
  passport.use(
    new LocalStrategy(
      {
        usernameField: 'email',
        passwordField: 'password',
      },
      async (email, password, done) => {
        try {
          const existUser = await prisma.users.findFirst({ where: { email } });

          if (existUser) {
            const result = await bcrypt.compare(password, existUser.password);

            if (!result) {
              done(null, false, { message: '비밀번호가 일치하지 않습니다.' });
            }
            //회원 정보가 일치하다면 토큰 발급
            else {
              const accessToken = jwt.sign(
                { userId: existUser.id },
                process.env.SECRET_KEY,
                {
                  expiresIn: '2h',
                },
              );
              const refreshToken = jwt.sign(
                {
                  refreshToken: existUser.id + '@refresh',
                  userId: existUser.id,
                },
                process.env.SECRET_KEY,
                {
                  expiresIn: '1d',
                },
              );

              await redisClient.set('refreshToken', refreshToken);
              await redisClient.expire('refreshToken', 60 * 60 * 24);

              existUser.accessToken = accessToken;
              done(null, existUser, { message: '로그인 되었습니다.' });
            }
          } else {
            done(null, false, { message: '회원가입을 해주세요.' });
          }
        } catch (err) {
          done(err);
        }
      },
    ),
  );
};

 

 

index.js

import passport from 'passport';
import local from './localStrategy.js';
import kakao from './kakaoStrategy.js';
import { prisma } from '../utils/prisma/index.js';

export default () => {
	passport.serializeUser((user, done) => {
		let newUser = {
			id: user.id,
			accessToken: user.accessToken
		};
		done(null, newUser);
	});

	passport.deserializeUser((user, done) => {
		user = user.id;
		prisma.users
			.findFirst({ where: { id: user } })
			.then((user) => {
				done(null, user);
			})
			.catch((err) => console.log(err));
	});

	local();
	kakao();
};

 

 

express에서는 done이라는 인자가 있었고 이것의 역할은 컷다. 

성공했을 경우 done(null,  데이터, 메세지), 실패하였을 경우 done(null, 불린값, 메세지)

이렇게 데이터가 전송되었고,  index.js 파일을 거쳐서 user정보를 요청을 보낸 라우터에 req.user로 넣어준다. 

 

 

 

이와 다르게 nest에서는 좀더 간단하다. 

index.js 파일이 없이 local.strategy.ts 파일과, 이걸 뒷받침 해줄 유저 정보 찾는 함수만 있으면 된다. 

 

nestJs 에서 passport local

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from '../auth.service';
import { User } from 'src/user/entities/user.entity';

@Injectable() //이 passport Strategy가 body에서 자동으로 이메일과 페스워드를 받아서 validate 함수로 넘겨주는것!
export class LocalStrategy extends PassportStrategy(Strategy) {
  //기본 이름이 local임. 그게 아니라면 Strategy, '넣어줄 이름'

  //로컬 전략은 기본적으로 요청 본문에서 username및 password호출
  //다른 속성 이름을 지정하려면 옵션 개체를 전달합니다  super({ usernameField: 'email' })
  constructor(private readonly authService: AuthService) {
    super({ usernameField: 'email', passwordField: 'password' });
  }

  //이 함수에서 리턴하는 값이 req.user에 담겨감.
  async validate(email: string, password: string): Promise<User> {
    const user = await this.authService.validateUser({ email, password });
    if (!user) {
      throw new UnauthorizedException('일치하는 인증정보가 없습니다.');
    }

    return user;
  }
}

 

 

nest에서는 의존성 주입을 통해서 다른 모듈의 로직을 가져다 사용할 수 있는데, 

authService 도 이런 방식으로 불러와서 authService내부에 미리 만들어둔  validateUser 함수를 사용하는 것이다. 

 

 

 

 

 

nestJs에서 다른 모듈의 로직 가져다 쓰기(의존성 주입)

 

다른 모듈의 로직을 사용하기 위해서는 먼저

 

1. 사용하려고 하는 함수가 들어있는 모듈을 export 한다. 

@Module({
  imports: [
    JwtModule.registerAsync({
      useFactory: (config: ConfigService) => ({
        secret: config.get<string>('JWT_SECRET_KEY'),
      }),
      inject: [ConfigService],
    }),
    TypeOrmModule.forFeature([Concert, Schedule, Seat]),
    SeatModule,
  ],

  controllers: [ScheduleController],
  providers: [ScheduleService],
  exports: [ScheduleService],
})
export class ScheduleModule {}

 

 

2. 지금 작업하고 있는 모듈파일에서 import를 해준다. 

@Module({
  imports: [
    JwtModule.registerAsync({
      useFactory: (config: ConfigService) => ({
        secret: config.get<string>('JWT_SECRET_KEY'),
      }),
      inject: [ConfigService],
    }),
    TypeOrmModule.forFeature([Concert, Schedule, Seat]),
    ScheduleModule,
    SeatModule,
  ],
  providers: [ConcertService],
  controllers: [ConcertController],
})
export class ConcertModule {}

 

 

3. service파일에서 의존성 주입을 해준다. 

@Injectable()
export class ConcertService {
  constructor(
    @InjectRepository(Concert)
    private concertRepository: Repository<Concert>,
    @Inject(ScheduleService)
    private readonly scheduleService: ScheduleService,
    private readonly seatService: SeatService,
    private dataSource: DataSource,
  ) {}

 

 

 

4. 이런식으로 스케줄 서비스파일에 만든 함수를 활용해서 사용할 수 있다. (함수의 재사용성이 높아져서 같은 로직을 작성하지 않아도 되어 좋을것 같다.)

//스케줄 테이블 데이터 넣기.
  const scheduleData = await this.scheduleService.createSchedule(
    performanceDate,
    saveConcert,
  );

 

 

 

express에서 모듈을 불러와서 사용하는 것과는 또 다르지만

어떤 방식이던 모듈간에 불러오고, 사용되는것은 비슷한 것 같다. 

약간의 방법의 차이가 있을 뿐!!

 

처음에 nest를 접했을 때에는 정말 많이 햇갈리고 어려웠는데

express와 비교하며 공부하니 좀더 이해가 잘 가는것 같다.