[NestJS] - 19. JWT 토큰 인가 - PassportStrategy/Guard 사용과 토큰 재발급
이전 포스팅에서는 인증 후 JWT를 발급하는 방법에 대해서 알아보았다.
이번에는 로그인된 사용자에게만 API 접근을 허용하도록 하는 인가에 대해서 다루어보겠다.
JWT 토큰 인증 방식에 대해서는 아래의 포스팅을 참고하면 좋을 것 같다.
https://sjh9708.tistory.com/46
패키지 설치
Express에서는 인가의 방법으로 Passport 패키지를 주로 사용했었다.
NestJS에서도 Passport를 사용할 수 있다. 다음의 패키지를 설치해주자.
yarn add passport passport-jwt @nestjs/passport |
Refresh Token의 쿠키를 가져오기 위해서 cookie-parser도 미리 설치해두자
yarn add cookie-parser |
인증 방식 생성
이제 API에 접근하기 전에 JWT 토큰을 확인하고, 검증 이후 통과되면 API를 이용할 수 있도록 막는 문지기를 만들어서 인가를 구현해야 한다.
아래와 같이 commons/auth 디렉터리 안에 strategy.ts 파일을 두개 만들어주었다.
각각 Access Token의 검증과 Refresh Token의 검증을 담당하는 문지기이다.
▶ jwt-access.strategy.ts
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
//PassportStrategy(인증 방식, 이름)
export class JwtAccessStrategy extends PassportStrategy(Strategy, 'access') {
//NestJS Docs
constructor() {
super({
// jwtFromRequest: (req) => {
// 원래 이 안에서 Request에서 무엇을 가져올 지 적어줘야 한다. Bearer 문자열 분리도 하는데 아래 메서드가 대신 해줌
// },
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), //Request Header에서 Bearer auth 뽑아내기
secretOrKey: process.env.ACCESS_TOKEN_SECRET_KEY,
});
}
//검증 성공 시 실행, 실패 시는 에러
// ### Passport는 validate에 성공할시 리턴값을 request.user에 저장함!!! ###
validate(payload) {
console.log(payload); //email, sub가 payload에 있음
return {
email: payload.email,
id: payload.sub,
};
}
}
- @nestjs/passport 패키지에서 제공하는 PassportStrategy를 상속 및 오버라이딩하여 사용한다.
- 인자는 PassportStrategy(인증 방식, 코드에서 사용할 Guard 이름) 이다.
- 생성자
- jwtFromRequest : Request에서 토큰을 가져오는 로직을 작성한다. 위의 메서드는 Header에서 Bearer 토큰을 가져와준다.
- secretOrKey : Access Token을 생성할 때 사용한 비밀키를 입력한다.
- 위 두가지를 입력해주면 Passport가 JWT 토큰을 알아서 검증해준다.
- validate : Passport는 사용자를 Request.user에 저장한다. 검증이 성공했을 때의 로직을 작성하며, Return값은 user 객체에 사용되게 된다.
▶ jwt-refresh.strategy.ts
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
//PassportStrategy(인증 방식, 이름)
export class JwtRefreshStretagy extends PassportStrategy(Strategy, 'refresh') {
//NestJS Docs
constructor() {
super({
jwtFromRequest: (req) => {
const cookie = req.cookies['refreshToken'];
return cookie;
},
secretOrKey: process.env.REFRESH_TOKEN_SECRET_KEY,
});
}
//검증 성공 시 실행, 실패 시는 에러
// ### Passport는 validate에 성공할시 리턴값을 request.user에 저장함!!! ###
validate(payload) {
console.log(payload); //email, sub가 payload에 있음
return {
email: payload.email,
id: payload.sub,
};
}
}
- 마찬가지로 Refresh Token을 담당하는 PassportStrategy를 생성한다.
- jwtFromRequest에서는 Header가 아니라 이번에는 Request의 쿠키에서 JWT 토큰을 가져온다.
▶ common.module.ts
import { Module } from '@nestjs/common';
import { JwtRefreshStretagy } from './auth/jwt-refresh.strategy';
import { JwtAccessStrategy } from './auth/jwt-access.strategy';
@Module({
imports: [],
providers: [JwtRefreshStretagy, JwtAccessStrategy],
exports: [JwtRefreshStretagy, JwtAccessStrategy],
})
export class CommonModule {}
▶ app.module.ts
@Module({
imports: [
//use env
ConfigModule.forRoot(),
CommonModule,
//...
],
controllers: [],
providers: [],
})
export class AppModule {}
해당 Strategy들을 전역으로 사용하게 하기 위해서 다음과 같이 모듈을 작성한다.
API 사용 인가
@UseGuards(AuthGuard('access'))
@Get(':id')
selectProduct(@Param('id') id: number): Promise<Product> {
return this.productService.findOne(id);
}
- @UserGuard 데코레이터를 통해서 API 사용 이전에 인가를 거치게 된다.
- 'access'는 방금 만들어 둔 Access Token에 대한 PassportStrategy를 사용하겠다는 뜻이다.
- 이제 해당 요청을 처리하기 이전에 Access Token의 검증이 이루어지고, 인증에 실패하면 결과 대신 403 에러를 반환하게 된다.
토큰 재발급 로직 구현
앞에 로그인에서 Access Token과 Refresh Token 두 개를 발급했었다.
평소 API 사용의 인가 처리에는 Access Token을 사용하게 되지만, 만료되었을 때의 재발급은 로그인이 아닌 Refresh Token을 이용해서 Access Token을 재발급하게 만들려고 한다.
▶ auth.userInterface.ts
export interface IOAuthUser {
user: Pick<User, 'email' | 'name' | 'age' | 'point'>;
}
▶ auth.controller.ts
//리프레쉬 토큰으로 새로운 엑세스 토큰 발급
@UseGuards(AuthGuard('refresh'))
@Post('refresh')
restoreAccessToken(@Req() req: Request & IOAuthUser) {
return this.authService.getAccessToken({ user: req.user });
}
- 이번에는 Refresh Token에 대한 PassportStrategy를 사용한다.
- Refresh Token에 대한 인가가 통과되면, 새로운 Access Token을 재발급한다.
- @Req() req : Request & IOAuthUser
- Express의 Request 객체를 사용한다.
- Request 객체의 User 타입을 사용하기 위해서 인터페이스를 만들어 타입을 명시해주었다.
- Passport를 통과하면 req.user를 통해 Strategy의 validate()에서 리턴된 user 객체를 사용할 수 있다.
'Backend > Node.js (NestJS)' 카테고리의 다른 글
[NestJS] - 21. 결제 프로세스와 Transaction Isolation (0) | 2023.03.05 |
---|---|
[NestJS] - 20. 인증과 인가 - OAuth2 Google 소셜 로그인 (0) | 2023.03.05 |
[NestJS] - 18. JWT 토큰 인증 - 회원가입/로그인 구현 (1) | 2023.03.04 |
[NestJS] - 17. TypeORM에 여러개의 데이터베이스 연결하여 사용하기 (0) | 2023.02.25 |
[NestJS] - 16. TypeORM View와 Raw Query, Index/Unique 제약조건 걸기 (0) | 2023.02.25 |