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

발행: (2026년 2월 1일 오후 06:53 GMT+9)
4 분 소요
원문: Dev.to

Source: Dev.to

Cover image for Stop using JWT libraries: How to build your own Lightweight Tokens with node:crypto

node_modules를 살펴보면서 작은 JSON 객체를 서명하기 위해 거대한 라이브러리가 왜 필요한지 궁금해 본 적 있나요?

Node.js 백엔드를 구축하고 있다면 이미 강력한 보안 도구가 내장되어 있습니다: node:crypto 모듈.

오늘은 표준 JWT 라이브러리보다 빠르고 가볍고 더 안전한 맞춤형 인증 토큰 시스템을 만들어 보겠습니다.

라이브러리를 건너뛰는 이유

  • Zero Dependenciesjsonwebtoken이 없으니 취약점 감사를 위한 패키지가 하나 줄어듭니다.
  • Performancenode:crypto는 Node의 네이티브 C++ 바인딩으로, 엄청나게 빠릅니다.
  • “Algorithm Switching” 공격 방지 – 대부분의 JWT 버그는 라이브러리가 클라이언트에게 알고리즘을 선택하게 허용(alg: none 등)해서 발생합니다. 우리 코드는 보안을 하드코딩합니다.

로직

안전한 토큰은 두 부분으로 구성됩니다:

  1. Payload – 사용자 데이터(ID, username, avatar)를 담은 Base64URL 인코딩 문자열.
  2. 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로 전환하셨나요? 댓글로 알려 주세요!

0 조회
Back to Blog

관련 글

더 보기 »

RUST 켜기

Java에서 Rust로 가는 나의 여정: 기술 스택 변경 안녕하세요, 제 이름은 Garik이고 오늘은 제가 기술 스택을 바꾸기로 결심한 이야기를 여러분과 공유하고 싶습니다. ...