JWT 라이브러리 사용을 중단하세요: node:crypto로 자체 경량 토큰 만들기
Source: Dev.to

node_modules를 살펴보면서 작은 JSON 객체를 서명하기 위해 거대한 라이브러리가 왜 필요한지 궁금해 본 적 있나요?
Node.js 백엔드를 구축하고 있다면 이미 강력한 보안 도구가 내장되어 있습니다: node:crypto 모듈.
오늘은 표준 JWT 라이브러리보다 빠르고 가볍고 더 안전한 맞춤형 인증 토큰 시스템을 만들어 보겠습니다.
라이브러리를 건너뛰는 이유
- Zero Dependencies –
jsonwebtoken이 없으니 취약점 감사를 위한 패키지가 하나 줄어듭니다. - Performance –
node:crypto는 Node의 네이티브 C++ 바인딩으로, 엄청나게 빠릅니다. - “Algorithm Switching” 공격 방지 – 대부분의 JWT 버그는 라이브러리가 클라이언트에게 알고리즘을 선택하게 허용(
alg: none등)해서 발생합니다. 우리 코드는 보안을 하드코딩합니다.
로직
안전한 토큰은 두 부분으로 구성됩니다:
- Payload – 사용자 데이터(ID, username, avatar)를 담은
Base64URL인코딩 문자열. - Signature – Payload가 변조되지 않았음을 증명하는 HMAC(해시 기반 메시지 인증 코드).
1. 서명 함수
import { createHmac } from "node:crypto";
const TOKEN_SECRET = process.env.TOKEN_SECRET;
export const signToken = (payload: object) => {
// Add 15‑minute expiration
const claims = {
...payload,
exp: Date.now() + 15 * 60 * 1000,
};
// 1. Encode data to a URL‑safe string
const encodedPayload = Buffer.from(JSON.stringify(claims)).toString("base64url");
// 2. Create the signature (the "security seal")
const signature = createHmac("sha256", TOKEN_SECRET!)
.update(encodedPayload)
.digest("base64url");
// 3. Combine them with a dot
return `${encodedPayload}.${signature}`;
};
2. 검증 함수
import { createHmac, timingSafeEqual } from "node:crypto";
export const verifyToken = (token: string) => {
const [encodedPayload, signature] = token.split(".");
if (!encodedPayload || !signature) throw new Error("Malformed token");
// Re‑calculate what the signature should be
const expectedSignature = createHmac("sha256", process.env.TOKEN_SECRET!)
.update(encodedPayload)
.digest("base64url");
// Use timingSafeEqual for high‑level security
const isValid = timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
if (!isValid) throw new Error("Invalid signature!");
const claims = JSON.parse(Buffer.from(encodedPayload, "base64url").toString());
// Check if expired
if (Date.now() > claims.exp) throw new Error("Token expired");
return claims;
};
프로 팁: 헤더 크기에 유의하세요!
토큰 안에 아바타와 사용자 이름 같은 “무거운” 데이터를 넣다 보면 문자열이 길어질 수 있습니다.
- 키를 짧게: 사용자 이름은
u, 아바타는img와 같이 짧은 키를 사용하세요. - 필요 최소한만: UI가 즉시 필요로 하는 데이터만 저장해 토큰을 가볍게 유지하세요.
결론
node:crypto를 사용하면 인증에 대한 완전한 제어권을 얻을 수 있습니다. 단순히 튜토리얼을 따라 하는 것이 아니라, 웹을 안전하게 지키는 수학과 로직을 직접 이해하게 되는 것이죠.
아직도 JWT 라이브러리를 사용하고 계신가요, 아니면 네이티브 crypto로 전환하셨나요? 댓글로 알려 주세요!