React/Redux/Spring Boot/AWSでSPAを作っているのですが
認証の方式をどうするか悩みました。
ベストと思われる認証方式をこちらに記載することにしました(随時更新します)。
JWTは使うべきじゃない
ログイン済みであるという事実を
サーバーサイドのセッションを使わずに保存しておく場合、
ログインした後でJWTなどのトークンをサーバーで生成し、
それをクライアントサイドで何らかの形で保存しておく必要がある。
なぜなら、ReactおよびReduxのstateで管理してしまうと、
ページをリロードされたときにstateがクリアされてしまうから。
具体的な保存先はローカルストレージくらいしか無いのだが
JWTをローカルストレージに格納するのは危険。
なぜなら、ローカルストレージはJavaScriptで簡単に読み込めてしまうから。
自ドメイン以外のJavaScriptを読み込んでいる場合、
そこのドメインのJavaScriptが書き換えられて
ローカルストレージを全スキャンするようなことをされたら、
JWTは簡単に抜き取られてしまう。
もちろん、「自ドメイン以外のJavaScriptを読み込んでいない」ということを保証できれば
こういったリスクは回避できるのだが、
実際にはこれを厳密に保証するのは、いささか非現実的かと思う。
マーケティング上の必要性から
Google Analyticsなどを結局入れることになったりするからである。
もちろん、Google社が先述のような悪事をするわけはないのだが
JWTというトップレベルにセキュアなデータが抜き取られるリスクが
常に自分の(あるいは顧客の)Webサイトに存在していることを考えると、
いくら極小だとしても許容できないのではないだろうか?
こういうわけで、「自ドメイン以外のJavaScriptを読み込んでいない」ということを
ルール化したり、保証していくのはなかなか難儀なことだと思う。
なので、ローカルストレージに
JWTのようなセキュアなデータを入れておくことはNG。
それがNGということなら、JWTを使うこと自体、NGということになる。
ではどうするのか?
結論からいうと、ログイン済みという事実だけは
サーバーサイドで保存せざるを得ない。
ログインしたらサーバーでセッションIDを生成し、
これを定石どおりセキュアcookieに入れてクライアントに送る。
(こうするとSSL/httpsのときだけ、cookieが送受信される)
これも定石だが、セッションIDを保存するcookieについては
httpOnlyを有効にすることが重要。
こうしておけば、JavaScriptからcookieを読み込めなくなるので、先述のリスクを回避できる。
(httpOnlyを有効にすると、httpリクエストを発行するときしかJavaScriptからcookieを読み込めなくなる)
こういったhttpOnlyの効能を得たいがために、サーバーサイドのセッション + cookieという従来の方式を採用せざるを得ないのである。
サーバーでセッションを管理するとスケールしないのでは?
確かに、サーバーのメモリでセッションを管理すると、特定のサーバーのメモリに依存してしまうわけだから、スケールアウトができなくなってしまう。
そこで、セッションIDの管理はDBやキャッシュサーバー(Redisなど?)で管理するのが良い。
そうしておけば特定のサーバーの状態に依存するようなことは無くなるので、スケールできる。
参考文献