編集履歴
※ SessionStorage→LocalStorage(永続化される方はこちらだった)
※ OPTIONメソッド→OPTIONSメソッド
※ JWTについて少し調べたのでLocalStorage欄を追記
概要
Frontend Meetup vol.1 - SPAを語り尽くす会!のLT資料です。
フロントエンドのガチ勢には当たり前の内容になるかもしれません。
SPA探り探りなので、ご指摘あればコメントなどで頂ければと思います。
自己紹介(後で消す)
- 名前:しばたこ/uryyyyyyy
- 所属:株式会社オプト
- 得意分野:Scala/Play2/Spark/React
- 最近はReact/Redux/TypeScriptで書いてます。
- materializeを導入したのですがjQueryなかなか辛い。。。
この資料で話すこと
- SPAでのセッション管理
- CSRF対策
- CORS
SPAでのセッション管理
基本
普通にcookie使えばいいと思います。
loginリクエストもajaxで投げればcookie返ってくるので、何も考えることないかなと。
Cookie
動作イメージは以下の形です。
== サーバ → UA ==
Set-Cookie: SID=31d4d96e407aad42; Path=/; Domain=example.com
== UA → サーバ ==
Cookie: SID=31d4d96e407aad42
(from https://triple-underscore.github.io/RFC6265-ja.html)
- ブラウザの状態管理のためのもの。
- もちろん主要ブラウザはどこも付いてます。
- railsでもplayでもとりあえずこれでセッション管理されています。
- secure属性があると、httpsのときだけやりとりされます。
- HttpOnly属性があると、jsから読み取れないようにできます。
基本、フロントエンド側では特に意識しなくてもサーバーサイドの人がよろしくやってくれるでしょう。
SessionStorage LocalStorage
Cookieは、過去の脆弱性の話とか後述するCSRFの話があり、「危ないのではないか」と思う人は、「SessionStorage LocalStorageでTokenを管理したらどうか」という声を上げたりします。あまり聞いたことはないですが、その場合は以下の制約があると思います。
-
対応してないブラウザあるかも - 認証の前にjsが読まれていないと値を取れない。
- 外部ドメインからのCORSはできない。
- ブラウザでCookieを消しても認証は続くという状態になる。
- 外部の悪いJSを読むとTokenが抜かれる危険がある。
- 主にXSS脆弱性がある場合
- ~~Tokenのexpireを自前で設定する必要がある。(サーバーにお任せできない。)~~後述
逆に言えば、このあたりがクリアできればアリかもしれないです。
(僕は使ったことはないです。ご意見求めます。。。)
(※ 追記:JWTでは、Tokenの中にexpireやuserIDなどを入れて改ざんできない形で扱うので、サーバー側で有効期間のコントロールはできるようです。)
CSRF対策
CSRF Token
SPAでないWebアプリだと、Form画面の中にCSRF-Tokenを仕込んで、リクエスト時にそれでチェックする方式がよく用いられます。
しかしSPAでは、フロントのコードがサーバーを経由せずに送られてきたり、Formが後から動的に作られうるので、仕込むタイミングが少し難しいかもしれません。
ブラウザのpre flightを利用する
後述のCORSに関わってきますが、ブラウザは訪れたドメインでないドメイン(3rd party)へのリクエストに対しては、セキュリティのためにpre flightという仕組みを持っています。
まず、普通のCSRFの仕組みから。
攻撃者は以下のことは外部ページから普通に行えてしまえます。
- 全てのGET
- REST的にはGETでリソース編集をすべきでないので無視
- POSTのContent-typeが以下のとき(他にあるのか把握してないですが十分かと)
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
しかし、以下のリクエストを投げるためには、事前にpre flightが飛ぶようになっています。
- PUTやDELETEメソッド
- POSTのContent-typeが上記以外(application/jsonなど)のとき
- 独自ヘッダが付与されているとき
このあたりは以下に詳しく書かれています。
- Protecting against Cross Site Request Forgery
- 独自ヘッダをチェックするだけのステートレスなCSRF対策は有効なのか?
- XMLHttpRequestを使ったCSRF対策
ちなみに、(Chrome環境でですが)動作を試したサンプルを置いておきます。
//pre flight飛ばない
//application/x-www-form-urlencodedが飛びます。
$.ajax({
type: "POST",
url: "http://localhost:8080/pre_flight",
data: "key=value",
success: function(msg){
console.log( "Data Saved: " + msg );
}
});
//pre flight飛ぶ
$.ajax({
type: "POST",
url: "http://localhost:8080/pre_flight",
data: { name: 'norm' },
contentType: "application/json",
success: function(msg){
console.log( "Data Saved: " + msg );
}
});
まとめとしては、独自ヘッダを付けるか、Content-Typeをapplication/jsonにして、サーバー側でバリデーションをかけてもらう(かつ、サーバー側でOPTIONSメソッドの呼び出しでリソース編集をしない)とCSRFを防げるかと思います。
CORS
(すみませんがあまり理解しきれていないです)
ブラウザでは、上述の外部ドメインのリソースの操作だけでなく、リソースの取得にも同様にセキュリティ対策がされています。
(例えば、怪しいサイトに訪れた時に自分のFacebookのCookieを利用してアカウント情報を取られたら嫌ですよね。)
なのでブラウザでは、自分が訪れたドメイン(A)から外部ドメイン(B)へGETしてきたデータを扱う場合には、Bのデータのヘッダに「Aのサイトを許可する」という情報を与えられていない場合は、そのGETした情報を無視します。
逆に、この仕組を使えば静的ファイルとAPIサーバとを別ドメインに置くことも出来ます。
SPAのコードはS3に置いて、サーバーはEC2とかですね。
詳しくはこのあたりを見ればいいのではと思います。
CORS(Cross-Origin Resource Sharing)によるクロスドメイン通信の傾向と対策
CORSまとめ
まとめ
- ざっくりでもセキュリティの話を知っておくのは大事。
- ブラウザの制約などは意味のあることなので、ちゃんと理解して使う。
- サーバーサイドのツラミを知ろう。
宣伝(後で消す)
-
上記の内容を細かく書いた記事を弊社技術マガジンで公開中です。
-
オプトではフロントエンジニアも絶賛募集中です。
- 既にAngular2も実戦投入しつつある
- TypeScriptやReactも導入が進んでいる
- どんな技術もしれっとぶち込める。参考
- そのくせ、社内にフロント専業マンはほぼ居ない
興味を持った方はぜひお話を。