Written by TSUYOSHI

カスタムヘッダーを使ったJavaScriptによるCSRF対策 (X-Form, X-Requested-With, X-Requested-By など)

JavaScript PROGRAMMING

本記事では、JavaScriptでAPIなどへの通信をする際にCSRF対策として、カスタムヘッダーを使った方法について解説しています。

この記事を書いている僕は、フロントエンドのプログラマー(フリーランス)として、これまで4年ほどのWeb制作経験があり、Vue,React,Nuxtなどフロントエンドの開発を得意としています。

この記事を読むことによって、カスタムヘッダーを使ったCSRF対策をしたJavaScriptによる通信の方法がわかるようになります。

カスタムヘッダーを使ったJavaScriptによるCSRF対策 (X-Form, X-Requested-With, X-Requested-By など)

CSRF対応はトークンによる対策が有名ですが、Cookieやトークンを使わずにステートレスにCSRF対策を行う方法で、カスタムヘッダーを利用してpreflightリクエストを必ず飛ぶようにして対策する方法があります。

フロントエンド → リクエストにカスタムヘッダーを付けるだけ
サーバーサイド → preflightリクエストの内容を見て、Origin、カスタムヘッダーの内容などからレスポンスを返して制御する

ちなみに、ここで使うカスタムヘッダーの「X-Form」「X-Requested-With」「X-Requested-By」による違いによる対策の効果に違いはありません。

結論としては、フロントエンドではリクエスト時にカスタムヘッダーを利用してpreflightリクエストを飛ばすようにして、サーバー側でpreflightリクエストのヘッダーを確認することによって正規のリクエストかどうかを判定して、CSRF対策をすることが可能となります。

Preflightを使ったCSRF対策の仕組み

CORSについての仕様をまず理解する必要があるので、CORSが分からない方は「【CORS】JavaScriptにおけるCORSやPreflightを理解する」の記事をどうぞ。

徳丸先生のYouTube動画でもCORSが分かりやすく解説されています。

方法としては簡単で以下のようになります。

  • カスタムヘッダーを利用することにより、他オリジンからのリクエスト時には、ブラウザがPreflightリクエストをするようにします。
  • サーバー側でブラウザから送られてくるPreflightリクエストを解析してレスポンスを返すようにします。

これによってCSRF対策用のトークンが不要になります。ポイントとしては、ブラウザからのリクエストで必ずPreflightリクエストを送るようにするために、「単純リクエスト」ではなくする必要があります。これに使われるのがカスタムヘッダーとなります。

単純リクエストの要件

「単純リクエスト」は以下のすべての条件を満たすものになります。

▼メソッドが以下のいずれか
GET
POST
HEAD
 
▼設定できるリクエストヘッダ
Accept
Accept-Language
Content-Language
Content-Type (但し、下記の要件を満たすもの)
DPR
Downlink
Save-Data
Viewport-Width
Width
 
※Content-Type ヘッダーでは以下の値のみが許可されている
application/x-www-form-urlencoded
multipart/form-data
text/plain

単純リクエストでは「Preflightリクエスト」を送りませんが、単純リクエスト以外では「Preflightリクエスト」を送って確認をしてから、次に「本リクエスト」を送るようになっています。

▼「単純リクエスト」の例
※通信は1回

▼「Preflightリクエスト」を使う通信(単純リクエスト以外)の例
※通信が2回になり、「Preflightリクエスト」の後に「本リクエスト」が送られる

X-Formというカスタムヘッダーを使った例

フロントエンドの実装

Content-Type」に「application/x-www-form-urlencoded」を指定し、カスタムヘッダー「X-From」にはオリジン情報を入れて送るようにようにします。こうすることによって、必ずPreflightリクエストが本リクエストの前に必ず飛ぶようになります。

▼JavaScriptコードの例

const xhr = new XMLHttpRequest();
xhr.open( 'POST', '/inquiry', true );
xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
xhr.setRequestHeader( 'X-From', location.origin );

※なお、「X-Form」の部分は、他によく使われる「X-Requested-With」や「X-Requested-By」で置き換えても同じです。

サーバーサイドの実装

Preflightリクエストのヘッダーの内容を確認します。

X-From
リクエストヘッダーが付与されているかをチェックし、X-Formの値が想定している内容かどうかを確認する。
Origin
リクエストヘッダーがついていないか、リクエストヘッダーがついていて、X-Formとオリジンが同一かを判定する。

これによって、以下のようにCSRFを防ぐことができるようになります。

  • 悪意のあるサイトでFormのSubmitによってリクエストした場合
    →X-Formヘッダーを付与できないのでサーバー側の判定で弾くことができる。
  • 悪意のあるサイトでJavaScriptの実行によりXHR(Ajax)にてリクエストがきた場合
    →Originヘッダーが悪意のあるサイトのオリジンになるため、サーバー側の判定で弾くことができる。

メリットとデメリット

メリット

カスタムヘッダーをリクエスト時に設定するようにするだけで、CSRF対策を簡単にできるようになります。

デメリット

HTMLでformタグなどを使ったリクエストは対応できず、ブラウザ側の処理で必ずJavaScriptによる処理が必要になります。

以下の記事が参考になります。
» XMLHttpRequestを使ったCSRF対策
» このWeb APIってCSRF対策出来てますか?って質問にこたえよう

ご参考になれば幸いです。

※当サイトでは一部のリンクについてアフィリエイトプログラムを利用して商品を紹介しています