はじめに:あなたの家の「合鍵」、どこに隠していますか?
皆さん、こんにちは!セキュリティの世界へようこそ。
日々、新しいシステムやアプリを開発していると「OAuth(オーオース)2.0」という言葉を耳にする機会が多いですよね。SNSログイン機能などでおなじみの、あの便利な仕組みです。
でも、ちょっと待ってください。もし、あなたが参考にしている技術ブログや入門書が数年前のものだとしたら、そこには「泥棒に合鍵の場所を教えているような、とても危険なやり方」が書かれているかもしれません。
それが、今回お話しする「暗黙的フロー(Implicit Flow)」という古い仕組みです。
「名前は聞いたことあるけど、何がダメなの?」という新人のIT担当者の方や、これからセキュリティを学びたい開発者の皆さんのために、家の防犯に例えて優しく、かつ「攻撃者はここを狙っているんだよ」というプロの視点を交えて解説していきますね。
一歩ずつ、一緒に学んでいきましょう!
—
1. OAuth 2.0は「ホテルのカードキー」の仕組み
まず、OAuth 2.0の本質を理解しましょう。これは、あなたの「パスワード」を相手に教えることなく、「特定の部屋だけ開けられるカードキー(アクセストークン)」を発行してもらう仕組みです。
- あなた(ユーザー):ホテルの宿泊客
- 認可サーバー(GoogleやGitHubなど):ホテルのフロント
- クライアント(あなたが作っているアプリ):ホテルの清掃サービスやルームサービス
本来、宿泊客であるあなたは、フロントで本人確認をしてからカードキーを受け取りますよね。この「カードキーの受け渡し方法」に、実は重大な欠陥があったのが「暗黙的フロー」なんです。
—
2. なぜ「暗黙的フロー」は泥棒に狙われるのか?
暗黙的フローでは、フロント(認可サーバー)がカードキー(アクセストークン)を発行すると、それを「透明な封筒」に入れて、誰でも見える場所に置いておくようなことをしていました。
具体的には、ブラウザの「URL」の中に直接カードキーを書き込んでいたのです。
攻撃のメカニズム:URLフラグメントの罠
暗黙的フローを使うと、ログイン後のURLはこんな風になります。
https://your-app.com/callback#access_token=abcd-1234-efgh-5678
この #(シャープ)以降の部分を「URLフラグメント」と呼びます。
「サーバーには送信されないから安全だよ」と昔は言われていましたが、レッドチーム(攻撃側)の視点で見れば、ここは宝の山です。
1. ブラウザ履歴に残る:共用パソコンを使っていたら、次の人が履歴を見るだけでカードキーを盗めます。
2. ブラウザの拡張機能から丸見え:怪しい拡張機能を一つ入れているだけで、トークンを外部に送信されてしまいます。
3. リファラ(Referrer)からの漏洩:そのページから別のサイトへリンクを踏んだ際、移動先のサイトに「どこから来たか」という情報としてトークン付きのURLが伝わってしまうことがあります。
泥棒からすれば、「家の鍵が玄関のマットの下に置いてある」のを見つけるくらい簡単なことなのです。
—
3. 解決策:PKCE(ピクシー)を使った認可コードフローへの移行
「じゃあ、どうすればいいの?」という答えが、現在の世界標準である「認可コードフロー + PKCE(Proof Key for Code Exchange)」です。
これは、カードキーを直接渡すのではなく、「使い捨ての引換券」と「合言葉」を使う仕組みです。
PKCEを例えるなら「秘密の合図」
1. 準備:アプリ側で、その場限りの「長い秘密の合図(Code Verifier)」を作り、それをハッシュ化した「合図のヒント(Code Challenge)」を用意します。
2. 依頼:フロント(認可サーバー)に「ヒント」だけを渡して、ログイン画面を出してもらいます。
3. 引換券:ログインが終わると、ブラウザにはカードキーではなく「使い捨ての引換券(認可コード)」だけが送られます。これだけ盗んでも鍵は開きません。
4. 交換:アプリが裏側の通信で、「引換券」と「最初に作った秘密の合図」をフロントに提出します。フロントは「ヒントと合図が一致した!本人だ!」と確認して、初めてカードキーを渡します。
これなら、万が一ブラウザのURLを覗き見られても、泥棒は「秘密の合図」を知らないので、カードキーを手に入れることはできません。
—
4. 実践!PKCEで使うパラメーターの設定例
開発者の皆さんが実装する際に、意識すべきパラメーターをJavaScriptの例で見てみましょう。
// 1. Code Verifier(秘密の合図)を生成する
// これはランダムで推測不可能な長い文字列です
const codeVerifier = generateRandomString(64);
// 2. Code Challenge(合図のヒント)を作成する
// codeVerifier を SHA-256 でハッシュ化して Base64URL エンコードします
const codeChallenge = await deriveChallenge(codeVerifier);
// 3. 認可サーバーへ送るリクエストURLの組み立て
const authUrl = new URL("https://auth-server.com/authorize");
authUrl.searchParams.set("response_type", "code"); // 「code」を指定するのが超重要!(暗黙的フローは token だった)
authUrl.searchParams.set("client_id", "your-client-id");
authUrl.searchParams.set("redirect_uri", "https://your-app.com/callback");
authUrl.searchParams.set("scope", "openid profile");
authUrl.searchParams.set("code_challenge", codeChallenge); // ヒントを送信
authUrl.searchParams.set("code_challenge_method", "S256"); // ハッシュアルゴリズムを指定
// このURLにユーザーをリダイレクトさせます
window.location.href = authUrl.toString();
このように、response_type=code を使い、code_challenge を添えるのが現代の「正しい鍵の受け取り方」です。
—
5. 防御を固めるための「ブラウザのヘッダー」設定
「仕組みはわかった。でも、もっと念入りに守りたい!」という方に、ぜひ設定してほしいのがセキュリティヘッダーです。これらは、ブラウザに対して「余計な情報を漏らすなよ!」と命令するガードマンのような存在です。
特に、URLの漏洩を防ぐために以下の設定を検討してください。
Referrer-Policy(リファラポリシー)の設定
もし自分のサイトから外部サイトへリンクを貼っていても、URLに含まれるトークンなどが漏れないように制限します。
# Webサーバー(ApacheやNginx)の設定例
# 自分のサイト内での移動以外では、URLの詳細を教えない設定
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy (CSP)
悪意のあるスクリプトがトークンを盗んで外部に送信するのを防ぎます。
# 信頼できるドメイン以外へのデータ送信を禁止する例
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-scripts.com;
—
まとめ:安全な道を進みましょう!
「暗黙的フロー」が推奨されていたのは、昔のブラウザが今ほど高機能ではなく、裏側での複雑な通信が難しかった時代の話です。今はもう、その役割を終えました。
- 暗黙的フロー:URLに直接鍵を書く。便利だけど、泥棒に丸見え。(廃止推奨!)
- PKCE付き認可コードフロー:合言葉と引換券を使う。手間は増えるけど、圧倒的に安全。(今の正解!)
もし、皆さんがこれから新しいアプリを作ったり、古いシステムのメンテナンスをしたりするなら、ぜひ「PKCEを使っているかな?」とチェックしてみてください。
セキュリティ対策は、一度に完璧を目指さなくて大丈夫です。まずは「なぜ今のやり方が推奨されているのか」という背景を知ることから、一歩ずつ進んでいきましょう。あなたの作った大切なサービスとユーザーを守れるのは、あなたのその小さな気づきからです。
これからも一緒に、安全で楽しい開発を続けていきましょうね!
コメント