JWT
(Json Web Token)
헤더/페이로드/시그니처
이런식으로 구성되어 있음
[로그인]
- 프론트엔드에서 Login Request (id, pw)
- 시크릿 키로 JWT 를 만들어서 전달
- FE쿠키에 저장
[인증(글쓰기)]
- 글쓰기 API req
- 헤더에 JWT를 실어서 보내줌
- JWT 가드
- JWT Strategy (시크릿 키 디코딩 => 해당하는 User를 저장)
- 이 User에 대한 정보를 알아서 비즈니스 로직 수행하여 Response 를 보냄
Passport 를 거쳐서 감.
$ npm install --save @nestjs/passport passport passport-local
$ npm install --save-dev @types/passport-local
서비스, Module 만들기
$ nest g module auth
$ nest g service auth
jwt.guard.ts 만들기
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
여기서 AuthGuard는 stategy 를 자동으로 실행시키게 된다.
그러므로 jwt.strategy.ts 파일을 만들어 준다.
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { Payload } from './jwt.payload';
import { CatsRepository } from 'src/cats/cats.repository';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly catsRepository: CatsRepository) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET,
ignoreExpiration: false,
});
}
async validate(payload: Payload) {
const cat = await this.catsRepository.findCatByIdWithoutPassword(
payload.sub,
);
if (cat) {
return cat; // request.user
} else {
throw new UnauthorizedException('접근 오류');
}
}
}
여기에는 전략과 연관된 정보가 들어가는 것이다.
scret key 같은 경우는 env 파일에서 지정해주는 것을 알 수 있음.
ignoreExpiration : 만료기간을 줄 수 있는데, 여기서 false 만료 없이
validate 를 수행을 해줌.
auth.module.ts
import { forwardRef, Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './jwt/jwt.strategy';
import { CatsModule } from 'src/cats/cats.module';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot(),
PassportModule.register({ defaultStrategy: 'jwt', session: false }),
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: '1y' },
}),
forwardRef(() => CatsModule),
],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
PassportModule.register 여기서 strategy 설정 jwtModule 은 인증 쪽임
forwardRef(() => CatsModule)
여기서 CatsRepository 를 가져옴.
login.request.dto.ts
import { PickType } from '@nestjs/swagger';
import { Cat } from '../../cats/cats.schema';
export class LoginRequestDto extends PickType(Cat, [
'email',
'password',
] as const) {}
auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { CatsRepository } from 'src/cats/cats.repository';
import { LoginRequestDto } from './dto/login.request.dto';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(
private readonly catsRepository: CatsRepository,
private jwtService: JwtService,
) {}
async jwtLogIn(data: LoginRequestDto) {
const { email, password } = data;
//* 해당하는 email이 있는지
const cat = await this.catsRepository.findCatByEmail(email);
if (!cat) {
throw new UnauthorizedException('이메일과 비밀번호를 확인해주세요.');
}
//* password가 일치한지
const isPasswordValidated: boolean = await bcrypt.compare(
password,
cat.password,
);
if (!isPasswordValidated) {
throw new UnauthorizedException('이메일과 비밀번호를 확인해주세요.');
}
const payload = { email: email, sub: cat.id };
return {
token: this.jwtService.sign(payload),
};
}
}
findCatByEmail 는 repository 에서 구현해줌
아래와 같이 함.
async findCatByEmail(email: string): Promise<Cat | null>{
const cat = await this.catModel.findOne({ email });
return cat;
}
그리고 passwordValidated 로 비밀번호 유효성 검증
JwtService 사용하기
auth.service.ts 파일에서
const payload = { email: email, sub: cat.id };
return {
token: this.jwtService.sign(payload),
};
마지막에 return 할 때, jwtService를 호출해준다. (payload를 사용해줌.)
payload에서
- sub : 토큰 제목을 의미함.
그러면 token을 만들어줌.
이러려면 당연히 상단에
constructor(
private readonly catsRepository: CatsRepository,
private jwtService: JwtService,
) {}
위와 같이 의존성 주입을 해주어야 하고
위에서 사용하는 JwtService는
auth.module.ts 에서 imports 시킨
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: '1y' },
}),
에서 등록된 애를 사용하는 것이다.
순환 종속성 참조
forwardRef(() => CatsModule)
를 사용하는 이유임.
근데 작동을 안하고 있었음…
왜그런가 보니…

@InjectModel 을 빼니까 Repository를 제대로 참조해 오는 것을 보게 된다.
애초에 @InjectModel 이 무슨 역할을 해주는지도 모르겠다.
단순히 github에 올라가 있는 코드 따라 다른 것들을 적용해본거라…
아무튼 된다…
결과
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im5hdnNraEBnbWFpbC5jb20iLCJzdWIiOiI2M2FiZThiNGU3NzEwZTE4MmFiYTI3YjEiLCJpYXQiOjE2NzIyMTI2NjIsImV4cCI6MTcwMzc3MDI2Mn0.JDN0s3FKEzdFXSsGKiPt4LJZByW9L2EPafZGWYpw5KA"
}
}
위와 같이 token이 생성되어 오는 것을 확인하면 끝!

sub에 아이디 나오고 exp 에 만료기간 (1년정도) 로 나오는 것을 확인할 수 있다.