#概要
AngularでCSRF(XSRF)対策でXSRFStrategyを使おうとしてなかなか上手くいかなかったので、その時のポイントをメモしときます。
サーバ側の実装はPHPで書きますが、他の言語でも共通していると思います。
##環境
Angular @4.0.0
CakePHP @2.7.X(多分)
#そもそもAngularでcsrfを実装する場合
Angularでcsrf対策をするには、サーバ側の協力が必要です。
むしろ、最小限の対策の場合はサーバ側だけの実装となります。
理由はAngularではデフォルトでcsrf用トークンを受けてサーバに送り返してくれる為です。
#ポイント
- **(クライアントとサーバのオリジンが違う場合)**クロスオリジンの許可
- サーバ側でcookieにトークンを書き込み。
ただし、クライアント側でトークンを読めるように設定 - サーバ側で特殊ヘッダを許可
- (任意) フロント側でトークン受け渡し用のキーを変える
1.**(クライアントとサーバのオリジンが違う場合)**クロスオリジンの許可
※サーバとクライアントが同じオリジンの場合は無視して下さい。
というか、これが無いとそもそもの通信が出来ないはずです。
//【フロント側】
// オプションで{withCredentials: true}を設定します。
this.http.post(reqUrl,data,{withCredentials: true})
//【サーバ側】
// とりあえず、どこのアクセスも受け付けてしまう設定です。
// アクセス元が限られる場合はそのアクセス元を指定すれば良いはずですが、全てOKの"*"ではダメでした。
$this->response->header("Access-Control-Allow-Origin", $_SERVER['HTTP_ORIGIN'] . "");
$this->response->header('Access-Control-Allow-Credentials', "true");
2. サーバ側でcookieにトークンを書き込み
// サーバ側でトークンを設定。
// 第4引数で、cookieを読み込めるパスをルートに設定(クッキーをdomain 配下の全てで有効にする)
setcookie("XSRF-TOKEN", $token, 0, "/");
angularでデフォルトのcsrf用キーでクッキーにトークンを書き込みます。
この時、普通に書き込むとフロント側で読めないはずなので、cookieの対象パスをルートに設定する。
※フロント側のjsでdocument.cookie
で読めるように設定してあればOK。
開発者ツールじゃないと読めないようであればNG。
3.サーバ側で特殊ヘッダを許可
// angularがcsrf対策に送ってくるヘッダーを許可
$this->response->header('Access-Control-Allow-Headers', "X-XSRF-TOKEN");
2.の設定により、Angular側からhttpモジュールを使ってリクエストを投げると勝手にリクエストヘッダに"X-XSRF-TOKEN"を追加してリクエストを送ってきます。
サーバ側ではこれを許可しないとヘッダーを受け取れません。
[ポイント]
この時フロントからのリクエストは以下の2段階で投げられる。
(どの環境も同じかは分からないが)
1回目:"X-XSRF-TOKEN"無しのリクエスト。
(許可されてないから送れないみたい)
2回目:"X-XSRF-TOKEN"有りのリクエスト。
なので、サーバ側では"X-XSRF-TOKEN"が無ければ処理を終わらせましょう。
ただし、エラーコードで返してしまうと、2回目のリクエストが来なかったです。
// トークンが無ければ処理を返す
if(!$this->request->header("X-XSRF-TOKEN"))return;
あとは、2回目のリクエストでトークンを取得し、前にサーバで設定したトークンと一致するか確認しましょう。
以上で基本的にはおしまいです!!
4.(任意) フロント側でトークン受け渡し用のキーを変える
おまけですが、app.module.tsで以下の定義を追加すると、各種キー値は変えられる。
// ワンタイム用のキーを変更(cookieで受け取る名前,ヘッダーで送り返す名前)
export function getXSRF() {
return new CookieXSRFStrategy('cookie-name', 'token-header-name');
}
// XSRFStrategyで使うキーの変更
// 変更用のfunctionは上で定義したものとなる。
providers: [{ provide: XSRFStrategy, useFactory: getXSRF }]
以上、丸一日くらいこれに悩みましたorz