nestjs/passport 적용하여 passport-jwt와 nestjs/jwt 활용하기 전에 수작업으로 인증하기
- Token generation 작동하기
- 모듈로 적용하기
Token generation 작동하기
json web token 설치하기
npm i jsonwebtoken
여기에는 Typescript 파일이 없다. 수동으로 찾아서 설치한다.
npm i @types/jsonwebtoken --only-dev
사용하기
jwt.sign에 token 데이터, privatekey, algorithm을 적어주면 된다. privatekey는 process.env에서 가져온다. 이를 가져오기 위해서 app.module.ts를 수정한다.
ConfigModule.forRoot({
isGlobal: true,
envFilePath: process.env.NODE_ENV === "dev" ? ".env.dev" : ".env.test",
ignoreEnvFile: process.env.NODE_ENV === "prod",
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('dev', 'prod')
.required(),
DB_HOST: Joi.string().required(),
DB_PORT: Joi.string().required(),
DB_USERNAME: Joi.string().required(),
DB_PASSWORD: Joi.string().required(),
DB_NAME: Joi.string().required(),
SECRET_KEY: Joi.string().required(), // 추가해주기
}),
}),
token을 user에게 지정해주면 사용자는 token 안에 무엇이 들었는지 알 수 있다. 사용자가 token을 수정하면 프로그램적으로 인지할 수 있다.
process.env에 SECRET_KEY를 입력해준다. 랜덤키 생성 사이트에서 key를 가져와서 넣어준다.
jsonwebtoken은 사용자에게 json을 주면 Application은 해당 json이 진짜인지 아닌지 구별을 할 수 있다.
const token = jwt.sign({id: user.id}, this.config.get('SECRET_KEY'))
jwt.sign 아래 파라미터로 SECRET_KEY를 주는데 app.module.ts 아래 키 설정해준 부분을 users.module.ts에서 import하고 import한 ConfigService를 users.service.ts의 constructor에 넣어준다음 get을 해서 사용한다. 여기에서 constructor에 ConfigService를 넣어서 사용하는데 이는 dependency injection을 해준다는 의미이다. 여기에서 nestjs의 장점은 class만 적어주면 해당 정보를 가져다 준다는 점이다. 장점을 확인하기 위해서 forRoot를 만들어보면서 확인해보도록 하자.
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { UsesrsResolver } from './users.resolver';
import { UsersService } from './users.service';
@Module({
imports:[TypeOrmModule.forFeature([User]), ConfigService],
providers: [UsesrsResolver, UsersService]
})
export class UsersModule {}
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,
){}
token 정보는 누구나 쉽게 알아볼 수 있다. 확인하기
여기서 중요한 점은 jsonwebtoken으로 token이 유효한지 인증을 할 수 있다는 점이다.
모듈로 적용하기
nest g mo jwt로 만들기
모듈은 2가지로 나누어 진다.
- static module
- Dynamic module
둘의 차이점은 설정 적용 여부이다.
DI 작동하기
- module 안에 .forRoot 구현하기
- module import 하기
module 안에 .forRoot 구현하기
.forRoot를 살펴보면 static method이고 Dynamic Module을 리턴한다. JwtModule을 리턴해주는데 이 때 module이 jwtservice를 export하도록 해준다. 이는 usesrs.module.ts에서 ConfigService를 import한 다음 users.service.ts의 constructor에서 사용했던 기억을 떠올리면 된다.
nest g s jwt로 만들기
module의 .forRoot에서 isGlobal: true로 설정해주면 다른 모듈에서 import 없이도 사용할 수 있다.
app.module.ts 안 JwtModule.forRoot()을 입력하고 jwt.module.ts를 아래와 같이 만든다.
import { DynamicModule, Global, Module } from '@nestjs/common';
import { JwtService } from './jwt.service';
@Module({})
@Global()
export class JwtModule {
static forRoot(): DynamicModule {
return{
module: JwtModule,
exports: [JwtService],
providers: [JwtService]
}
}
}
@Global()로 decorator를 설정해주어서 users.module.ts의 import 없이도 사용할 수 있다. users.service.ts의 constructor에서 DI해주고 사용할 수 있다.
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"; // Global 이기 때문에 import 없이도 사용할 수 있다.
import { JwtService } from "src/jwt/jwt.service"; // Global 이기 때문에 import 없이도 사용할 수 있다.
@Injectable()
export class UsersService{
constructor(
@InjectRepository(User) private readonly users: Repository<User>,
private readonly config: ConfigService,
private readonly jwtService: JwtService
){
this.jwtService.hello();
}
추가 코드 및 정리를 해보면
app.module.ts에 .forRoot 아래 private key를 .env.dev의 PRIVATE_KEY를 options로 준다. jwt.module.ts 아래 providers 아래 provide에 CONFIG_OPTIONS를 설정하고 useValue에 options 파라미터를 적어준다. 이 파라미터는 JwtModuleOptions 인데 이는 jwt.interface.ts에서 가져온다. 이 설정은 Global이므로 jwt.service.ts에서 @Inject 해서 사용할 수 있다.
최종적으로 users.service.ts에서 jwt.service.ts의 sign 함수를 가져와서 token 값을 return 하도록 해준다.
// 앱 모듈 아래 JwtModule.forRoot()를 만들어준다.
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 { RestaurantsModule } from './restaurants/restaurant.module';
import { Restaurant } from './restaurants/entities/restaurant.entity';
import { UsersModule } from './users/users.module';
import { User } from './users/entities/user.entity';
import { JwtModule } from './jwt/jwt.module';
@Module({
imports: [
GraphQLModule.forRoot({
autoSchemaFile: true,
}),
ConfigModule.forRoot({
isGlobal: true,
envFilePath: process.env.NODE_ENV === "dev" ? ".env.dev" : ".env.test",
ignoreEnvFile: process.env.NODE_ENV === "prod",
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('dev', 'prod')
.required(),
DB_HOST: Joi.string().required(),
DB_PORT: Joi.string().required(),
DB_USERNAME: Joi.string().required(),
DB_PASSWORD: Joi.string().required(),
DB_NAME: Joi.string().required(),
PRIVATE_KEY: Joi.string().required(),
}),
}),
TypeOrmModule.forRoot({
type: "postgres",
host: process.env.DB_HOST,
port: +process.env.DB_PORT,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
synchronize: process.env.NODE_ENV !== 'prod',
logging: true,
entities: [User],
// }).then(connection => {
// here you can start to work with your entities
// }).catch(error => console.log(error));
}),
UsersModule,
JwtModule.forRoot({
privateKey: process.env.PRIVATE_KEY
})
],
controllers: [],
providers: [],
})
export class AppModule {}
//jwt 모듈을 설정한다.
import { DynamicModule, Global, Module } from '@nestjs/common';
import { CONFIG_OPTIONS } from './jwt.constants';
import { JwtModuleOptions } from './jwt.interfaces';
import { JwtService } from './jwt.service';
@Module({})
@Global()
export class JwtModule {
static forRoot(options: JwtModuleOptions): DynamicModule {
return{
module: JwtModule,
exports: [JwtService],
providers: [{
provide: CONFIG_OPTIONS,
useValue: options,
},
JwtService
]
}
}
}
// interface에서 JwtModuleOptions를 설정한다.
export interface JwtModuleOptions {
privateKey: string;
}
// constant에서 상수값을 설정한다.
export const CONFIG_OPTIONS = 'CONFIG_OPTIONS';
// service에서 Inject를 통해서 설정 값을 사용한다.
import { Inject, Injectable } from '@nestjs/common';
import * as jwt from 'jsonwebtoken';
import { JwtModuleOptions } from './jwt.interfaces';
import { CONFIG_OPTIONS } from './jwt.constants';
@Injectable()
export class JwtService {
constructor(
@Inject(CONFIG_OPTIONS) private readonly options: JwtModuleOptions,
) { }
sign(userId: number): string {
return jwt.sign({ id: userId }, this.options.privateKey);
}
}
// users.service.ts 아래 token을 jwt.service.ts sign 함수를 사용해서 가져온다.
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 { JwtService } from "src/jwt/jwt.service";
@Injectable()
export class UsersService{
constructor(
@InjectRepository(User) private readonly users: Repository<User>,
private readonly jwtService: JwtService
){}
async createAccount({ email, password, role }: CreateAccountInput): Promise<{ ok: boolean; error?: string }> {
try {
const exists = await this.users.findOne({ email });
if (exists) {
return { ok: false, error: 'There is a user with that email already' };
}
await this.users.save(this.users.create({ email, password, role }));
return { ok: true };
} catch (e) {
return { ok: false, error: "Couldn't create account" };
}
}
async login({email, password}: LoginInput): Promise<{ ok: boolean; error?: string; token?: string }> {
try{
const user = await this.users.findOne({email});
if(!user){
return{
ok: false,
error: "User not found",
};
}
const passwordCorrect = await user.checkPassword(password);
if(!passwordCorrect){
return{
ok: false,
error: "Wrong password",
};
}
const token = this.jwtService.sign(user.id);
return {
ok: true,
token
}
}catch(error){
return {ok: false, error}
}
}
}
'Uber Eats' 카테고리의 다른 글
Uber Eats # 18 User Profile (0) | 2021.03.13 |
---|---|
Uber Eats # 17 AuthGuard (0) | 2021.03.13 |
Uber Eats # 15 User Resolver and Service, InputType과 ObjectType 비교 (0) | 2021.03.09 |
Uber Eats # 14 User Model (0) | 2021.03.09 |
Uber Eats # 13 User CRUD (0) | 2021.03.09 |