こんにちは!ネットワークの世界の奥深さに魅せられ、日夜パケットの流れを追いかけているインフラアーキテクトです。
皆さんは、普段何気なく使っているWeb APIや、スマホアプリの裏側で動いているデータ通信について、「どうやって安全に守られているんだろう?」と気になったことはありませんか?
今回は、Web APIのセキュリティにおいて非常に重要なテーマである「CSRF(クロスサイトリクエストフォージェリ)」と、それを防ぐための「カスタムヘッダー」の仕組みについて、現実世界の郵便配達にたとえながら、一歩ずつ優しく紐解いていきたいと思います。
難しい用語が出てきても置いてけぼりにしませんので、リラックスして読み進めてくださいね!
—
1. Web APIの「ステートレス」とCSRF(クロスサイトリクエストフォージェリ)の影
まず、REST APIの大きな特徴の一つに「ステートレス(状態を持たない)」という原則があります。これは、サーバー側が「さっきリクエストを送ってきたのは〇〇さんですよ」という状態を基本的には覚えておらず、リクエストが来るたびに「あなたは誰ですか?権限はありますか?」と確認する仕組みのことです。
このステートレスな世界で、私たちはよく「Cookie(クッキー)」という仕組みを使ってユーザーのログイン状態を維持します。ブラウザは、一度ログインすると、そのWebサイトあての通信に自動的にCookieを添付してくれます。これはユーザーにとって非常に便利ですよね。
しかし、この「ブラウザが自動的にCookieをつけてくれる」という親切心こそが、セキュリティの現場では最大の罠(CSRF:Cross-Site Request Forgery)を生む原因になるのです。
身の回りの例えで考えてみましょう
あなたが、近所の信頼できる「A銀行」の窓口(Webサイト)に口座を持っているとします。あなたは窓口の担当者に「私の口座から、Aさんの口座へ1万円振り込んでください」とお願いする用紙にサインして渡しました。これは正当な手続きです。
ところが、あなたが全く関係のない怪しい「B商店」の店頭に立ち寄ったとき、B商店の店主がこっそりあなたのポケットから「A銀行宛ての勝手な振込用紙」を差し込み、あなたが気づかないうちに銀行へ走って届けてしまったとしたら……?
これがCSRF(クロスサイトリクエストフォージェリ)の恐ろしい手口です。悪意あるWebサイト(B商店)が、ユーザーのブラウザ(郵便配達員)を巧みに操り、ユーザーが意図しないリクエストを別の信頼できるWeb API(A銀行)へ送りつけてしまうのです。
—
2. 「カスタムヘッダー」という名の特別な封印
「自動でCookieが送られてしまうなら、安全な通信なんてできないのでは……?」と思いますよね。ここで登場するのが、今回の主役である「カスタムヘッダー」です。
HTTP通信の世界には、ブラウザが勝手に他のドメイン(サイト)宛ての通信に付加できない性質を持つ「特別なルール」を利用した防御策があります。それが、自分で自由に名前を決められるカスタムヘッダー(例:X-Requested-With など)の活用です。
郵便配達の例えで言うと?
先ほどの銀行の例に戻りましょう。
先ほどの手口では、怪しいB商店の店主は「普通の振込用紙」を勝手に使っていました。そこでA銀行は、新しいルールを作りました。
> 「今後は、窓口での振込だけでなく、専用の『青い特別なスタンプ(カスタムヘッダー)』が押された用紙からの依頼しか受け付けません。この青いスタンプは、私たちが提供している公式の専用カウンター(JavaScriptアプリ)でしか押せない仕組みになっています。」
悪意あるB商店の店主は、あなたのポケットから普通の振込用紙を出すことはできても、この「青い特別なスタンプ」を勝手に押すことはできません。なぜなら、ブラウザのセキュリティ上の厳しいルール(CORS:Cross-Origin Resource Sharing)によって、別のサイトから勝手にカスタムヘッダーを追加したリクエストを送ることはブロックされてしまうからです。
このように、「カスタムヘッダーがついているか?」を確認するだけで、そのリクエストが本当に信頼できる自社アプリの画面から送られたものか、それともどこかの悪意あるサイトから仕組まれたものかをピタリと見破ることができるのです。
—
3. 実装の現場を見てみましょう(コード例)
それでは、この仕組みが実際のシステムでどのように実装されているのか、具体的なコードを見ていきましょう。今回は、フロントエンド(JavaScript)からAPIを叩く際の設定と、サーバー側(Pythonの軽量フレームワーク FastAPIのイメージ)でのチェック処理を例にします。
フロントエンド(JavaScript)側の実装
APIリクエストを送る際、あらかじめ決めたカスタムヘッダー(ここでは X-Requested-With: XMLHttpRequest や独自のヘッダー)を必ず付加するようにします。
// フロントエンドのJavaScriptコード例
async function sendUserData(userData) {
try {
const response = await것('https://api.example.com/v1/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// ★これが「青い特別なスタンプ」にあたるカスタムヘッダーです
'X-Requested-With': 'XMLHttpRequest'
},
// Cookieなどの認証情報を一緒に送る設定
credentials: 'include',
body: JSON.stringify(userData)
});
const data = await response.json();
console.log('通信成功:', data);
} catch (error) {
console.error('通信エラー:', error);
}
}
バックエンド(サーバー側)側の実装
サーバー側では、リクエストを受け取ったときに、このカスタムヘッダーが確実に含まれているかをチェックします。もし含まれていなければ、「不正なリクエストの可能性が高い」として即座に拒否(エラーを返却)します。
# バックエンド(Python / FastAPIのイメージ)のコード例
from fastapi import FastAPI, Header, HTTPException, status
app = FastAPI()
@app.post("/v1/user")
async def update_user(
# リクエストヘッダーから特定のカスタムヘッダーの値を取得する
x_requested_with: str | None = Header(default=None)
):
# ★カスタムヘッダーが存在するか、または期待する値かチェックする
if x_requested_with != "XMLHttpRequest":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="不正なリクエストが検知されました(カスタムヘッダーがありません)"
)
# 正常なリクエストの場合の処理
return {"message": "ユーザー情報の更新に成功しました!"}
このように、サーバー側でたった数行のチェックを入れるだけで、CSRFという巧妙な脅威からAPIをがっちりと守ることができるのです。
—
4. まとめ:一歩ずつ、確実なセキュリティを
今回は、Web APIにおけるステートレスな環境でのCSRFリスクと、それを防ぐためのカスタムヘッダーの役割について解説しました。
- ステートレスなAPIとCookieの組み合わせは便利だが、CSRFの危険性が潜んでいる
- ブラウザのセキュリティ制限を利用した「カスタムヘッダー」は、正当なリクエストを証明する強力な「スタンプ」になる
- フロントエンドでヘッダーを付与し、バックエンドでそれを厳格にチェックすることが実務の基本
ネットワークやセキュリティの世界は、一見すると難解な英語や仕様書の山に見えますが、こうして身近な仕組みに置き換えてみると、「なるほど、理にかなっているんだな」とスッと腑に落ちるはずです。
これからも、一つひとつの技術の裏側にあるストーリーを大切にしながら、一緒に楽しく学んでいきましょう!それではまた次回の技術記事でお会いしましょう。
コメント