こんにちは。セキュリティの世界へようこそ。
日々、見えない脅威と戦うエンジニアの皆さん、本当にお疲れ様です。
今日は、OpenID Connect(OIDC)という「認証の仕組み」の中で、「nonce(ナンス)」というちょっと聞き慣れないパラメータについてお話しします。
「え、セキュリティの話? 難しそう……」と思ったあなたも大丈夫。まずは身近な「家の鍵」の話から紐解いていきましょう。
—
1. 泥棒は「使い古しの合鍵」を狙っている
あなたが家を出るとき、玄関の鍵をかけますよね。この鍵は「あなたしか持っていないもの」です。
サイバー攻撃の世界では、「リプレイ攻撃」という手法があります。これは、泥棒があなたの家の鍵を盗むのではなく、「以前使った鍵の形をコピーして、もう一度侵入を試みる」という卑劣な手口です。
Webサービスにログインする際、認証サーバーはあなたに対して「IDトークン」という「通行手形」を発行します。もし、悪意ある誰かがこの通行手形を盗み見して、それをそっくりそのままサーバーに送りつけたらどうなるでしょう?
サーバーはこう思います。
「お、以前見たことのある有効な手形だ。じゃあ、本人だね、どうぞどうぞ!」
……これが「リプレイ攻撃」の正体です。これを防ぐための「使い捨ての合鍵」が、今回紹介するnonceなのです。
—
2. nonce(ナンス)の正体は「その場限りの合言葉」
nonceは「number used once(一度だけ使われる番号)」の略です。
仕組みはとてもシンプルです。あなたがログインをリクエストするとき、クライアント(あなたのアプリ)は、「今回のログインのためだけに作った適当な文字列(nonce)」を添えて認証サーバーに送ります。
1. あなた:「これからログインするよ! 今回の合言葉は『リンゴ』ね!」
2. 認証サーバー:「了解。じゃあ、『リンゴ』というメモを添えた通行手形を作るね。」
3. 認証サーバー:(通行手形を発行)
もし泥棒がこの手形を盗んで、後からもう一度使おうとしても、サーバーはこう言います。
「ちょっと待って。その手形には『リンゴ』って書いてあるけど、今はもう『ミカン』の合言葉のタイミングだよ。過去の遺物だね、突き返し!」
こうして、一度使われた手形は二度と使えないというルールが確立されるわけです。
—
3. 実践!nonceを組み込むための実装イメージ
では、コードでどう書くのか見てみましょう。今回はWeb開発でよく使われるイメージで解説しますね。
① ログイン開始時(認証サーバーへ送る)
まず、認証サーバーへリクエストを送る際に、推測困難なランダム文字列を生成して保存しておきます。
// セッションに保存するためのランダムな値を生成
const nonce = crypto.randomBytes(16).toString(‘hex’);
// セッションにnonceを保存(後で答え合わせをするため)
sessionStorage.setItem(‘nonce’, nonce);
// 認証URLを組み立ててリダイレクト
const authUrl = `https://auth.example.com/authorize?
client_id=YOUR_CLIENT_ID&
response_type=id_token&
scope=openid&
nonce=${nonce}`; // ここで合言葉をセット!
② 認証後(返ってきた手形を検証する)
認証サーバーから戻ってきたIDトークンの中に、先ほどのnonceが含まれているか確認します。
// サーバーから戻ってきたIDトークンをデコードして検証
const idToken = decodeJwt(returnedToken);
// 1. 今のセッションのnonceを取り出す
const savedNonce = sessionStorage.getItem(‘nonce’);
// 2. IDトークン内のnonceと合致するか確認!
if (idToken.nonce !== savedNonce) {
throw new Error(“警告:リプレイ攻撃の可能性あり!”);
}
console.log(“検証成功!正真正銘の本人です。”);
—
4. 忘れてはいけない相棒「stateパラメータ」
ここまで読んで「なるほど!」と思ったあなたは鋭い。ただ、もう一つだけ「state」というパラメータもセットで覚えて帰ってください。
- nonce: 「通行手形」そのものが偽造・再利用されていないか確認するもの。
- state: 「今のログインリクエスト」が、本当に自分のサイトから始まったものか確認するもの(CSRF対策)。
「state」は、あなたが「ログインボタンを押した」という事実を証明する「チケットの半券」のようなものです。これがないと、攻撃者が勝手に自分のアカウントをあなたのブラウザに紐付けさせる「ログインCSRF」という攻撃を受けるリスクがあります。
「nonceはトークンの中身を守るため、stateはログインの始まりを守るため」。この二つを揃えて初めて、強固な防犯体制が整うのです。
—
最後に:完璧なセキュリティなんて存在しない
厳しいことを言うようですが、どんな技術も「絶対」ではありません。しかし、こうした小さな積み重ねが、あなたのサービスとユーザーを守る強力な壁になります。
「面倒だな」と思うかもしれませんが、泥棒は常に「面倒くさくない隙」を探しています。あなたがnonceを一行書くだけで、その隙を一つ埋めることができる。そう考えると、エンジニアってかっこいい仕事だと思いませんか?
明日からの開発で、ぜひこの「nonce」を思い出して実装してみてください。応援しています!
コメント