「一度使った鍵」を悪用させない!OpenID Connectの nonce が守るデジタル認証の最前線
こんにちは!セキュリティの最前線で「システムの穴」を探しているエンジニアです。
今日は、Webサービスのログインでよく使われる「OpenID Connect(OIDC)」という仕組みの中で、実は非常に重要な役割を果たしている nonce(ナンス)というパラメータについてお話しします。
「認証なんて、IDとパスワードが合っていれば大丈夫でしょ?」と思っていると、実はちょっと怖い落とし穴があるんです。今回は、身近な「家の鍵」を例にして、なぜ nonce が必要なのか、どうやって自分たちのサービスを守ればいいのかを一緒に見ていきましょう!
—
1. 「リプレイ攻撃」ってなんだろう?家の鍵で例えてみると
まずは、「リプレイ攻撃」という少し怖い名前の攻撃について解説します。
あなたが家に帰ってきて、スマートロックにスマホをかざして鍵を開けたと想像してください。このとき、スマホから鍵に対して「開けて!」という電波(信号)が飛びますよね。
もし、泥棒が家の近くに隠れていて、この「開けて!」という電波をこっそり録音していたらどうなるでしょう?あなたが外出している間に、泥棒がその録音した電波を鍵に向けて再生したら……そう、鍵は「持ち主が帰ってきた!」と勘違いしてドアを開けてしまいますよね。
これが「リプレイ攻撃」です。「一度使った有効な信号を、悪い奴がもう一度(リプレイ)使う」ことで、なりすましを成功させる手法です。
—
2. 解決策は「使い捨ての合言葉」: nonce の正体
この攻撃を防ぐために導入されたのが nonce です。nonce とは、”number used once”(一度だけ使われる数)の略です。
さっきの家の鍵の例でいうと、鍵を開けるたびに「今日の合言葉は『バナナ』だよ」とランダムな言葉を決め、スマホ側も「『バナナ』という合言葉を添えて開けて!」と送るようにします。もし泥棒が以前の電波を録音していても、そこに含まれているのは昔の合言葉(例:『りんご』)なので、鍵は「今の合言葉と違うから無視!」と拒否することができます。
OpenID Connectの世界でも、全く同じことが行われています。
—
3. 実装で気をつけるべきこと: nonce の検証ステップ
ログイン処理において、nonce は以下のような流れで働きます。
1. リクエスト時: サービス側(クライアント)が、認証サーバーに対して「ログインしたいです。今回の合言葉は random_string_123 です!」という情報を送ります。
2. レスポンス時: 認証サーバーは、送られてきた random_string_123 をそのままIDトークンの中に埋め込んで返します。
3. 検証時: サービス側は、届いたIDトークンを開封し、「自分が最初に送った合言葉と、IDトークンに入っている合言葉が一致しているか?」を確認します。
もしこの「3. 検証」をサボってしまうと、いくら nonce を送っていても、泥棒が横取りしたトークンを自由に使えてしまいます。
PHPでの検証イメージ(簡略版)
<?php
// 1. 最初にリクエストを送る際、セッションに保存しておく
$nonce = bin2hex(random_bytes(16)); // ランダムな文字列を生成
$_SESSION['expected_nonce'] = $nonce;
// 認証URL生成時にnonceをパラメータに含める
// https://auth-server.com/auth?response_type=id_token&nonce=<?php echo $nonce; ?>...
// 2. 認証サーバーから戻ってきたIDトークンを検証する際
$id_token = $_POST['id_token'];
$decoded_token = decode_jwt($id_token); // JWTをデコードする関数
// ここが一番重要!「合言葉」が一致するか確認する
if ($decoded_token['nonce'] !== $_SESSION['expected_nonce']) {
die("不正なリクエストです!nonceが一致しません。");
}
// 検証が済んだら、使い回しを防ぐためにセッションを破棄する
unset($_SESSION['expected_nonce']);
?>
—
4. なぜ「検証」を忘れてしまうのか?
現場でよくあるのが、「ライブラリに任せているから大丈夫だろう」という思い込みです。
多くのOIDCライブラリは強力ですが、「nonceの検証を有効にする設定」がデフォルトでOFFになっていたり、正しく設定されていないケースが往々にしてあります。
開発者の皆さんは、以下の点に注意してください。
- ライブラリの設定を確認: 使用しているSDKやライブラリのドキュメントで、「nonce validation」の設定がONになっているか確認しましょう。
- ログに出力してみる: 開発環境で、実際にIDトークンの中身を
print_rやvar_dumpで覗いてみてください。「本当にnonceが入っているか?」「自分の送った値と同じか?」を自分の目で見るのが、理解への一番の近道です。
—
最後に:セキュリティは「疑うこと」から始まる
「認証」という言葉は信頼を感じさせますが、セキュリティの世界では「誰も信頼しない(ゼロトラスト)」という考え方が基本です。
nonce を検証することは、サービスを泥棒から守るための最初の一歩です。今回のお話を通じて、「自分たちが送ったはずのデータが、戻ってきた時にも正しく保たれているか?」という視点を持ってもらえたら、とても嬉しいです。
セキュリティ対策は難しく考えすぎず、まずは「どうすれば相手を騙せるか?」と少しだけ悪い想像を膨らませてみてください。そうすれば、自ずと守るべき場所が見えてくるはずですよ!
また次回の記事でも、現場で使える泥臭い知見をシェアしていきますね。一歩ずつ、一緒に強くなっていきましょう!
コメント