jwt role guard 적용기
✨ jwt + role guard 를 적용해 보자 !
jwt 인증과 payload 에 심어진 role로 guard 를 적용하자
(예시를 위하여 jwt access token은 post 요청시 body에 id 와 role 을 전달하면 받을수 있게 함 !)
install
yarn add @nestjs/config @nestjs/jwt @nestjs/passport passport passport-jwt
공통 적으로 사용하는 값들
export enum Role {
role1 = 'role1',
role2 = 'role2',
}
export class AuthDto {
@ApiProperty()
@IsString()
id: string;
@ApiProperty()
@IsEnum(Role)
role: Role;
}
// role 적용을 위한 key 값
export const ROLES_KEY = 'roles';
auth.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { TypeOrmModule } from '@nestjs/typeorm';
import { JwtStrategy } from '#common/auth/Jwt.strategy';
import { AuthController } from '#controller/auth.controller';
import { AuthService } from '#service/auth.service';
@Module({
imports: [
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
secret: config.get('JWT_SECRET_KEY'),
signOptions: { expiresIn: '1h' },
}),
}),
TypeOrmModule.forFeature()
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}
JwtModule import 할때 registe
로 적용하면, import가 config파일을 불러오기 전에 호출되기 때문에 환경 변수 값을 들고오지 못한다.
그래서 registerAsync
와 ConfigModule
를 사용하여 환경 변수 값을 적용했다.
auth.controller.ts
@Controller('/auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post()
async getToken(@Body() auth: AuthDto) {
if ((await this.authService.validateUser(auth)) !== true)
return {
error: {
id: 'UNAUTH',
},
};
const accessToken = await this.authService.getToken(auth);
return {
data: {
accessToken,
},
};
}
}
/auth
로 body에 id와 role 값을 전달 한다.
이때 유효한 회원인지 검사를 하고 유효하면 jwt token 을 반환한다.
유효한 회원인지 검사와, token 생성 하는 부분은 service 단에서 처리함 !
auth.service.ts
@Injectable()
export class AuthService {
constructor(
private jwtService: JwtService,
@InjectRepository(Entity1)
private entity1Repository: Repository<Entity1>,
@InjectRepository(Entity2, BridgeDB)
private entity2Repository: Repository<Entity2>,
){}
async validateUser(auth: AuthDto) {
switch(auth.role) {
case Role.role1 :
return !! (await this.entity1Repository.findOne({id : auth.id}));
case Role.role2 :
return !! (await this.entity2Repository.findOne({id : auth.id}));
}
return false;
}
async getToken(auth: AuthDto) {
const payload = {id: auth.id, role: auth.role};
return this.jwtService.sign(payload);
}
controller을 통하여 jwt를 위한 token을 발급 받았다.
이 토큰을 api호출 할때 마다 header에 포함시켜서 호출해야 한다.
이때 이 토큰이 유효한지 검사는 passport 모듈에서 처리해 준다. 적용해 보자 !
jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: true,
secretOrKey: process.env.JWT_SECRET_KEY,
});
}
async validate(payload: any) {
return { id: payload.id, role: payload.role };
}
}
나는 jwt 토큰을 bearer 방법을 통해 검증하기때문에 ExtractJwt.fromAuthHeaderAsBearerToken() 방법을 적용했다.
validate method를 통해 return 값이 req.user 에 들어 간다 !!
jwt.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtGuard extends AuthGuard('jwt') {}
jwt를 사용하겠다는 guard 이다. 이 guard를 적용하고자 하는 controller에 적용하면 된다. --> @UseGuards(JwtGuard)
여기까지가 jwt 인증 부분 이다.
✨ 이제 payload에 가지고있는 role 값을 통해 특정 role을 가진 user만 api를 호출할수 있도록 RoleGuard를 적용하자 !
role.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '#common/auth/roles.decorator';
@Injectable()
export class RoleGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<Role[]>(ROLES_KEY, context.getHandler());
if (!roles) return true;
const { user } = context.switchToHttp().getRequest();
return this.matchRoles(roles, user.role);
}
matchRoles(roles: Role[], userRole: Role) {
return roles.some((role) => role === userRole);
}
}
context.switchToHttp().getRequest() 에서 가져온 user값은 위, JwtStrategy validate 에서 return 된 값에 해당한다.
controller에 적용하기
@UseGuards(JwtGuard)
@Controller()
export class AppController {
...
@Get()
@UseGuards(RoleGuard)
SetMetadata(ROLES_KEY, [(Role.role1, Role.role2])
async test(@Req() req) {
// req.user.id << 이렇게 접근할수 있다 !
...
}
@Get('/good)
@UseGuards(RoleGuard)
@Roles(Role.role1, Role.role2) // role decorator
async test(@Req() req) {
// req.user.id << 이렇게 접근할수 있다 !
...
}
}
JwtGuard와 마찬가지로 controller에 @UseGuards(JwtGuard) << 이런식으로 적용해 주자
role decorator 적용하기
role decorator를 통해 SetMetadata를 좀더 간단하게 해보자 !
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);