JavaScript Tips | 文字列ユーティリティ:生成 - トークン生成

JavaScript JavaScript
スポンサーリンク

まず「トークン生成」で何を作りたいのかを決める

トークンと言っても、用途はいろいろあります。

API トークン
パスワードリセット用の一時 URL トークン
メール認証用のワンタイムトークン
CSRF トークン

共通しているのは、「推測されてはいけない」「一意性がとても大事」という点です。
ここでやりたいのは、そういう用途に耐えられる「安全なトークン文字列」を生成するユーティリティを作ることです。

結論から言うと、トークン生成では必ず

  • Math.random() は使わない
  • crypto.getRandomValues を使う(ブラウザ)

という前提で考えます。


トークンは「バイト列」から作る、という発想を持つ

文字列から考えると迷子になる

「トークンを文字列で作ろう」といきなり考えると、

英数字にする?
記号も混ぜる?
長さは何文字?

と、いきなり設計がブレがちです。

トークンは本質的には「ランダムなバイト列」です。
それを「人間やシステムが扱いやすい文字列」に変換しているだけ、と考えるとスッキリします。

ランダムバイト列 → 文字列、という流れ

流れはこうです。

  1. crypto.getRandomValues で N バイトのランダムデータを作る。
  2. それを 16進文字列(hex)や Base64 などに変換する。

この「バイト列から文字列に変換する」パターンを一つ覚えておくと、どの用途にも応用できます。


基本形:16進文字列(hex)のトークン生成

ランダムバイトを生成する

まずは「ランダムなバイト列」を作る小さな関数です。

function randomBytes(length) {
  const array = new Uint8Array(length);
  crypto.getRandomValues(array);
  return array;
}
JavaScript

Uint8Array は「0〜255 の整数」を length 個持つ配列です。
crypto.getRandomValues が、ここに OS 由来の安全な乱数を詰めてくれます。

バイト列を 16進文字列に変換する

次に、「バイト列 → 16進文字列」に変換する関数です。

function bytesToHex(bytes) {
  return Array.from(bytes)
    .map((b) => b.toString(16).padStart(2, "0"))
    .join("");
}
JavaScript

やっていることはシンプルで、

  • 各バイトを 16進数文字列に変換(0ff
  • 2桁になるように padStart(2, "0") でゼロ埋め
  • それを全部つなげる

というだけです。

hex トークン生成ユーティリティ

これを組み合わせて、「hex トークン生成」を作ります。

function generateHexToken(byteLength = 32) {
  const bytes = randomBytes(byteLength);
  return bytesToHex(bytes);
}
JavaScript

使ってみると、こんな感じです。

generateHexToken(16); // 例: "9f3a7c2b1e4d8a0f3c5b7e9a1d2c4f6"
generateHexToken(32); // 例: "e1f3a7c2b1e4d8a0f3c5b7e9a1d2c4f6a9b8c7d6e5f4a3b2c1d0e9f8a7b6"
JavaScript

ここでの「強さ」は「バイト数」で決まります。
16バイト = 128ビット、32バイト = 256ビット、といったイメージです。

API トークンやパスワードリセットトークンなら、
16〜32バイト(hex で 32〜64文字)あれば十分に強いです。


Base64 形式のトークンを作る(URL 用にも)

Base64 文字列に変換する

同じランダムバイト列を、今度は Base64 文字列に変換してみます。

ブラウザなら、btoa を使って簡単に書けますが、
Uint8Array を一度文字列に変換する必要があります。

function bytesToBase64(bytes) {
  let binary = "";
  for (let i = 0; i < bytes.length; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}
JavaScript

これで、「バイト列 → Base64 文字列」ができます。

Base64 トークン生成

function generateBase64Token(byteLength = 32) {
  const bytes = randomBytes(byteLength);
  return bytesToBase64(bytes);
}
JavaScript

使ってみると、こんな感じです。

generateBase64Token(16); // 例: "nzp8Kx5Nig88xWuZpQ2YxA=="
JavaScript

URL に埋め込みやすい Base64URL 形式

URL にトークンを埋め込みたい場合、+/= が入る普通の Base64 は少し扱いづらいです。
そこで、Base64URL という「URL フレンドリーな変種」を使うことが多いです。

変換ルールはシンプルです。

  • +-
  • /_
  • 末尾の = は削除

これを関数にするとこうなります。

function toBase64Url(base64) {
  return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

function generateBase64UrlToken(byteLength = 32) {
  const bytes = randomBytes(byteLength);
  const base64 = bytesToBase64(bytes);
  return toBase64Url(base64);
}
JavaScript

使うとこうなります。

generateBase64UrlToken(16); // 例: "nzp8Kx5Nig88xWuZpQ2YxA"
JavaScript

これなら、そのまま URL の一部に埋め込んでも壊れにくいトークンになります。


実務での使いどころと設計のポイント

用途ごとに「形式」を決めておく

トークンの「中身の強さ」はバイト数で決まりますが、
「見た目の形式」は用途で決めると整理しやすいです。

API トークン → hex 形式(ログに出しても読みやすい)
URL トークン → Base64URL 形式(短くて URL フレンドリー)

というように、「この用途ではこの形式」と決めておくと、
チーム内での認識も揃いやすくなります。

「生成」と「検証/失効」は別レイヤー

ここで作っているのは「トークン文字列を生成する」部分だけです。

実際のシステムでは、

  • 生成したトークンをどこに保存するか(DB、キャッシュなど)
  • いつまで有効にするか(有効期限)
  • 何と紐づけるか(ユーザーID、メールアドレスなど)
  • どうやって失効させるか

といった設計が別途必要になります。

ユーティリティの責務は「強度のあるトークン文字列を作ること」まで。
それをどう運用するかは、認証・セキュリティ設計側の仕事です。


ちょっとだけ手を動かしてみる

コンソールで、次の順番で試してみてください。

generateHexToken(16);
generateHexToken(32);

generateBase64UrlToken(16);
generateBase64UrlToken(32);
JavaScript

出てきた文字列を眺めながら、

長さの違いでどれくらい「強そう」に見えるか
hex と Base64URL でどちらが扱いやすそうか
URL に埋め込むならどちらがよさそうか

を、自分の感覚で掴んでみてください。

そのうえで、自分のプロジェクトに

export function generateHexToken(byteLength?) { ... }
export function generateBase64UrlToken(byteLength?) { ... }
JavaScript

の2本を置いて、

「API や内部用 → hex」
「URL に載せる一時トークン → Base64URL」

という使い分けをルール化してみてください。

それができた瞬間、あなたの「トークン生成」は
なんとなくのランダム文字列から、
目的と安全性を意識した“業務レベルのトークンユーティリティ”に変わります。

タイトルとURLをコピーしました