개발일지
[NestJS] PASETO로 인증/인가 구현하기 본문
지난 포스트에서 PASETO에 대해 알아봤습니다. 이를 NestJS로 직접 구현해보고자 합니다. 전반적인 구현 방식은 JWT 인증/인가와 동일합니다. 가독성을 위해 DB 저장 등의 로직은 생략하고 PASETO 생성 및 인증 부분만 설명하겠습니다.
패키지 설치
🔗 https://github.com/panva/paseto
$ yarn add paseto
v4, 비대칭키 방식(public)을 사용했습니다.
PASETO 생성
로그인 시 access token, refresh token을 생성합니다. 비대칭키 방식이기 때문에 한 쌍의 private/public 키 값이 필요합니다. 여기서는 paseto를 생성하는 부분이기 때문에 private key를 사용합니다.
import { V4 as paseto } from 'paseto'; // v4 사용
@Injectable()
export class TokenService {
async createAuthToken({ userId }: Dto): {
const payload = { sub: userId };
try {
const accessToken = await paseto.sign(payload, 'access-private-key', {
expiresIn: '3m',
});
const refreshToken = await paseto.sign(payload, 'refresh-private-key', {
expiresIn: '10m',
});
// logic...
return { accessToken, refreshToken };
} catch (error) {
switch (error.message) {
case 'invalid key provided':
throw new HttpException(
exceptionMessagesAuth.UNVERIFIED_SECRET_KEY,
400
);
default:
throw new HttpException(
exceptionMessagesAuth.FAILED_CREATE_TOKEN,
400
);
}
}
}
}
Access token 재발급
// class TokenService
{
constructor(
private readonly pasetoService: AuthPasetoService,
) {}
...
async recreateAccessToken({refreshToken}: Dto) {
const verifiedRefreshToken = await this.pasetoService.verifyPaseto(
refreshToken,
'RefreshToken'
);
const payload = { sub: verifiedRefreshToken['sub'] };
try {
const accessToken = await paseto.sign(
payload, this.ACCESS_PRIVATE_KEY, {
expiresIn: '3m',
});
// logic...
return { accessToken };
} catch (error) {
// throw error
}
}
}
PASETO 검증
AccessTokenGuard
사용자 검증을 위한 AccessTokenGuard를 생성합니다. 여기서는 paseto에 대한 검증이 이루어져야합니다.
// authUser.guard.ts
@Injectable()
export class AccessTokenGuard implements CanActivate {
constructor(private readonly pasetoService: AuthPasetoService) {}
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
const authorization = request.headers['authorization'];
const token = String(authorization).split(' ')[1];
if (!token) {
throw new HttpException(
exceptionMessagesAuth.TOKEN_NOT_EXISTS,
400
);
}
// paseto 검증
const verifiedUser = await this.pasetoService.verifyPaseto(token);
request['userId'] = verifiedUser.sub;
return true;
}
}
검증 하기
.verify()
로 paseto 검증를 검증합니다. return 형식은 다음과 같습니다.
{
sub: 'userId', // payload
iat: '2019-07-02T13:36:12.380Z',
exp: '2019-07-02T15:36:12.380Z',
aud: 'urn:example:client',
iss: 'https://op.example.com'
}
토큰 생성 시 사용했던 private 키와 대응되는 public 키로 paseto를 검증합니다. guard에서는 access token 검증이, access token 재발급할 때에는 refresh token에 대한 검증이 이루어지기 때문에 type
파라미터를 추가로 받아 구분할 수 있도록 했습니다.
import { V4 as paseto } from 'paseto';
@Injectable()
export class AuthPasetoService {
async verifyPaseto(
token: string,
type: TokenType = 'AccessToken'
){
const publicKey =
type == 'AccessToken'
? this.ACCESS_PUBLIC_KEY
: this.REFRESH_PUBLIC_KEY;
try {
const verifiedToken = await paseto.verify(token, publicKey);
return {
sub: verifiedToken['sub'],
iat: verifiedToken['iat'],
exp: verifiedToken['exp'],
};
} catch (error) {
switch (error.message) {
case 'invalid key provided':
throw new HttpException(
exceptionMessagesAuth.UNVERIFIED_SECRET_KEY,
400
);
case 'token is expired':
throw new HttpException(
exceptionMessagesAuth.EXPIRED_TOKEN,
400
);
default:
throw new HttpException(
exceptionMessagesAuth.UNVERIFIED_TOKEN,
400
);
}
}
}
}
키 생성하기
paseto
에서는 local 또는 public 키를 생성하는 .generateKey()
를 제공합니다.
async createKey() {
const { secretKey, publicKey } = await paseto.generateKey('public', {
format: 'paserk',
});
return { secretKey, publicKey };
}
'NestJS, Node.js > #02 NestJS' 카테고리의 다른 글
[NestJS] Guard, Decorator로 역할 기반 접근 제어(RBAC) 구현하기 (0) | 2024.11.10 |
---|---|
대용량 객체 파일 안전하게 업로드하기 - AWS 멀티 파트 업로드 with.NestJS (0) | 2024.10.26 |
[devlog] 객체지향 구조로 데이터 불일치 해결하고 개발 생산성 개선하기 (0) | 2024.05.25 |
[NestJS] Passport+JWT+Guard 검증 로직 커스텀하기 (1) | 2024.01.06 |
[NestJS] 메일 전송 하기 - SES, Nodemailer , EJS (0) | 2022.11.23 |