NestJS USER AUTHENTICATION

LeeJaeHoon·2021년 12월 16일
post-thumbnail

설치

npm i jsonwebtoken
npm i jsonwebtoken @types/jsonwebtoken --only-dev

사용

ConfigModule.forRoot의 validationSchema에 SECRET_KEY를 추가해줍니다.

SECRET_KEY는 jwt.sign에 privateKey로 넣어줄 겁니다.

app.module.ts

import { Module } from '@nestjs/common';
import * as Joi from 'joi';
import { ConfigModule } from '@nestjs/config';
import { GraphQLModule } from '@nestjs/graphql';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';
import { CommonModule } from './common/common.module';
import { User } from './users/entities/user.entity';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true, //우리 어플리케이션의 어디서나 config 모듈에 접근할 수 있다는 것
      envFilePath: process.env.NODE_ENV === 'dev' ? '.env.dev' : '.env.test',
      ignoreEnvFile: process.env.NODE_ENV === 'prod', //서버에 deply 할 때 환경변수 파일을 사용하지 않는다는 것
      validationSchema: Joi.object({
        NODE_ENV: Joi.string().valid('dev', 'prod').required(),
        DB_HOST: Joi.string().required(),
        DB_PROT: Joi.string().required(),
        DB_USERNAME: Joi.string().required(),
        DB_PASSWORD: Joi.string().required(),
        DB_NAME: Joi.string().required(),
        SECRET_KEY: Joi.string().required(),
      }),
    }),
    TypeOrmModule.forRoot({
      ...
    }),
    GraphQLModule.forRoot({
      autoSchemaFile: true, //메모리에 저장
    }),
    UsersModule,
    CommonModule,
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

SECRET_KEY를 process.env.SECRET_KEY로 가져올수 있지만 configService를 통해 가져올 수도 있습니다.

원래 users.modules.ts에 configService를 providers해줘야하지만 ConfigModule의 옵션으로 isGlobal: true를 해줘서 providers없이 사용할 수 있습니다.

import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { UsesrResolver } from './users.resolver';
import { UsersService } from './users.service';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UsesrResolver, UsersService],
})
export class UsersModule {}

users.service.ts

private readonly config: ConfigService을 선언해주고 config.get()메서드의 argument에 변수 이름을 전달하여 환경 변수를 가져 올 수 있습니다.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as jwt from 'jsonwebtoken';
import { CreateAccountInput } from './dtos/create-account.dto';
import { LoginInput } from './dtos/login.dto';
import { User } from './entities/user.entity';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly users: Repository<User>,
    private readonly config: ConfigService,
  ) {}

  //create User
  ...

  //Login User
  async login({
    email,
    password,
  }: LoginInput): Promise<{ ok: boolean; error?: string; token?: string }> {
    // make a JMT and give it to the user
    try {
      // find ths user with the email
      const user = await this.users.findOne({ email });
      if (!user) {
        return { ok: false, error: 'User not found' };
      }
      // check if the passowrd is correct
      const passwordCorrect = await user.checkPassword(password);
      if (!passwordCorrect) {
        return { ok: false, error: 'Wrong password' };
      }
      const token = jwt.sign({ id: user.id }, this.config.get('SECRET_KEY'));
      return { ok: true, token: 'goododo' };
    } catch (error) {
      return { ok: false, error };
    }
  }
}

jwt.sign의 첫번째 argument로 token안에 어떤 정보를 넣을 것인지 정해주고 두번째 argument로 privateKey가 들어갑니다.

token을 user에게 지정해주면 사용자는 자기 token안에 뭐가 들었는지 볼 수 있습니다.

즉 사용자가 자신의 token에 들어있는 암호를 해독할 수 았다는 것입니다.

따라서 token은 중요한 개인정보를 넣는 용도로는 좋지 않습니다. 그래서 저는 user의 id값을 token으로 줬습니다.

privateKey를 이용해서 token을 지정해 주는 목적은 유저가 token의 정보를 변경 했을경우

우리는 privateKey를 통해 사용자가 token을 수정했는지 확인할 수 있기 때문입니다.

즉 수정된 정보를 우리도 인지할 수 있다는 말입니다.

nestjs의 module에는 두가지가 있습니다

첫번째는 static module입니다

static module이란 어떠한 설정도 적용되어 있지 않은걸 가리킵니다.

두번째는 dynamic module입니다.

dynamic module이란 GraphQLModule처럼 설정이 존재합니다.

여기서 알아둬야 할건 dynamic module은 사실 결과적으로 static module이 된다는 것입니다.

dynamic module은 일단 static module이고 반환값이 dynamic module입니다.

JWT Module만들기

nest g mo jwt, nest g s jwt를 통해 jwt Module과 Service를 만들어줍니다.

JWT모듈을 dynamic module로 만들어주기위해 jwt.module.ts에서 다음과 같이 해줍니다.

import { DynamicModule, Global, Module } from '@nestjs/common';
import { JwtService } from './jwt.service';

@Module({})
@Global() // 전역으로 사용가능하게 만들어줍니다. ConfigModule의 isGlobal: true와 같다고 보면됩니다.
export class JwtModule {
	// dynamic module은 일단 static module이고 반환값이 dynamic module입니다.
  static forRoot(): DynamicModule { 
    return {
      module: JwtModule,
      exports: [JwtService],
      providers: [JwtService],
    };
  }
}

jwt.service.ts

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

@Injectable()
export class JwtService {
  hello() {
    console.log('Hello');
  }
}

app.module.ts

이제 어디에서나 JwtService를 의존성 주입을 통해 사용할 수 있습니다.

import { Module } from '@nestjs/common';
import * as Joi from 'joi';
import { ConfigModule } from '@nestjs/config';
import { GraphQLModule } from '@nestjs/graphql';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';
import { CommonModule } from './common/common.module';
import { User } from './users/entities/user.entity';
import { JwtModule } from './jwt/jwt.module';

@Module({
  imports: [
    //...설정

    JwtModule.forRoot(),
    UsersModule,
    CommonModule,
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

users.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as jwt from 'jsonwebtoken';
import { CreateAccountInput } from './dtos/create-account.dto';
import { LoginInput } from './dtos/login.dto';
import { User } from './entities/user.entity';
import { ConfigService } from '@nestjs/config';
import { JwtService } from 'src/jwt/jwt.service';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly users: Repository<User>,
    private readonly config: ConfigService,
    private readonly jwtService: JwtService,
  ) {
    console.log(this.jwtService.hello()); //Hello
  }

  ...
}

console.log(this.jwtService.hello())해보면 Hello가 출력되는 걸 볼 수 있을 겁니다.

custom provider

providers: [JwtService]의 원래 형태는 [{provide : JwtService, useClass: JwtService}] 입니다.

이걸 활용해서 providers를 custom할 수 있습니다.

import { DynamicModule, Global, Module } from '@nestjs/common';
import { JwtModuleOptions } from './jwt.interfaces';
import { CONFIG_OPTIONS } from './jwt.constants';
import { JwtService } from './jwt.service';

@Module({})
@Global()
export class JwtModule {
  static forRoot(options: JwtModuleOptions): DynamicModule {
    return {
      module: JwtModule,
      exports: [JwtService],
      providers: [
        {
          //CONFIG_OPTIONS라는 이름을 가진 provider가 있고 그 value가 options인 것이다
          provide: CONFIG_OPTIONS,
          useValue: options,
        },
        JwtService, 
      ],
    };
  }
}

jwt.constants.ts

export const CONFIG_OPTIONS = 'CONFIG_OPTIONS';

jwt.interfaces.ts

export interface JwtModuleOptions {
  privateKey: string;
}

app.module.ts

import { Module } from '@nestjs/common';
import * as Joi from 'joi';
import { ConfigModule } from '@nestjs/config';
import { GraphQLModule } from '@nestjs/graphql';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';
import { CommonModule } from './common/common.module';
import { User } from './users/entities/user.entity';
import { JwtModule } from './jwt/jwt.module';

@Module({
  imports: [
    //...설정

    JwtModule.forRoot({
      privateKey: process.env.PRIVATE_KEY,
    }),
    UsersModule,
    CommonModule,
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

jwt.service.ts

import { Inject, Injectable } from '@nestjs/common';
import { JwtModuleOptions } from './jwt.interfaces';
import { CONFIG_OPTIONS } from './jwt.constants';

@Injectable()
export class JwtService {
  constructor(
    @Inject(CONFIG_OPTIONS)
    private readonly options: JwtModuleOptions,
  ) {
    console.log(this.options); // {privateKey: 설정한값}
  }
  sign(userId: number): string {
    return jwt.sign({ id: userId }, this.options.privateKey);
  }
}

users.service.ts

jwtService를 만든 덕분에 이런식으로 코딩 할 수 있습니다.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateAccountInput } from './dtos/create-account.dto';
import { LoginInput } from './dtos/login.dto';
import { User } from './entities/user.entity';
import { ConfigService } from '@nestjs/config';
import { JwtService } from 'src/jwt/jwt.service';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly users: Repository<User>,
    private readonly config: ConfigService,
    private readonly jwtService: JwtService,
  ) {}

  //create User
  ...
  //Login User
  async login({
    email,
    password,
  }: LoginInput): Promise<{ ok: boolean; error?: string; token?: string }> {
    // make a JMT and give it to the user
    try {
      // find ths user with the email
      const user = await this.users.findOne({ email });
      if (!user) {
        return { ok: false, error: 'User not found' };
      }
      // check if the passowrd is correct
      const passwordCorrect = await user.checkPassword(password);
      if (!passwordCorrect) {
        return { ok: false, error: 'Wrong password' };
      }
      // const token = jwt.sign({ id: user.id }, this.config.get('PRIVATE_KEY'));
      const token = this.jwtService.sign(user.id);
      return { ok: true, token };
    } catch (error) {
      return { ok: false, error };
    }
  }
}

0개의 댓글