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

발행: (2026년 2월 1일 오후 06:53 GMT+9)
4 min read
원문: 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로 전환하셨나요? 댓글로 알려 주세요!

Back to Blog

관련 글

더 보기 »

RUST 켜기

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