まず「トークン生成」で何を作りたいのかを決める
トークンと言っても、用途はいろいろあります。
API トークン
パスワードリセット用の一時 URL トークン
メール認証用のワンタイムトークン
CSRF トークン
共通しているのは、「推測されてはいけない」「一意性がとても大事」という点です。
ここでやりたいのは、そういう用途に耐えられる「安全なトークン文字列」を生成するユーティリティを作ることです。
結論から言うと、トークン生成では必ず
Math.random()は使わないcrypto.getRandomValuesを使う(ブラウザ)
という前提で考えます。
トークンは「バイト列」から作る、という発想を持つ
文字列から考えると迷子になる
「トークンを文字列で作ろう」といきなり考えると、
英数字にする?
記号も混ぜる?
長さは何文字?
と、いきなり設計がブレがちです。
トークンは本質的には「ランダムなバイト列」です。
それを「人間やシステムが扱いやすい文字列」に変換しているだけ、と考えるとスッキリします。
ランダムバイト列 → 文字列、という流れ
流れはこうです。
crypto.getRandomValuesで N バイトのランダムデータを作る。- それを 16進文字列(hex)や Base64 などに変換する。
この「バイト列から文字列に変換する」パターンを一つ覚えておくと、どの用途にも応用できます。
基本形:16進文字列(hex)のトークン生成
ランダムバイトを生成する
まずは「ランダムなバイト列」を作る小さな関数です。
function randomBytes(length) {
const array = new Uint8Array(length);
crypto.getRandomValues(array);
return array;
}
JavaScriptUint8Array は「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進数文字列に変換(
0〜ff) - 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=="
JavaScriptURL に埋め込みやすい 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」
という使い分けをルール化してみてください。
それができた瞬間、あなたの「トークン生成」は
なんとなくのランダム文字列から、
目的と安全性を意識した“業務レベルのトークンユーティリティ”に変わります。
