JWTどこに保存するべきか問題JWTをどこに保存するかを色々調べていたので、それらについて端的にまとめる。そもそもAPIファーストでフロントとバックが疎結合なアーキテクチャはなぜセッションではなくJWTみたいな認証トークンを使用するのかや、APIのRESTの考え方からもう一度復習することにした。まとめAPIはステートレスであるべきというRESTの考えRESTの考え方の1つに「ステートレス」というものがある。APIは「状態を保持しない」設計にしろということである。そもそもHTTP通信は「ステートレス」であり、それだと色々困ることがあったからセッションというものが登場した。セッションは「ステートフル」であり、「状態を保持する」機能である。ステートフルなセッションは、誰のものか判別するために一意のIDが必要になる。一般的にそのIDを保存する場所がCookieであり、クライアント側で作成される。RESTの思想を第一に考えるのであれば、クライアント・サーバー間のセッション管理は望ましくない。APIファーストのメリットをちゃんと知るAPIファーストなアーキテクチャのメリットは、「APIにすることで、他のシステムやアプリケーションと簡単に統合できる」ことにある。APIとして用意しておけば、適切なリクエストを送ることでどのシステムやアプリであっても使用できる点が最大のメリットとなる。ステートレスな設計でないと、このメリットを最大限に発揮できず、状態に依存した拡張性の少ないAPIになってしまう。そのため、APIはステートレスであるべきだという前提を知る必要がある。認証におけるステートフルとステートレス認証には、「セッション管理」による方法と、「認証トークン」による方法がある。前述の通り、セッション管理はステートフルな認証であり、一方で認証トークンの利用の場合はステートレスとなる。API設計を重んじるのであれば、認証はトークンによるステートレスな手法が望ましい。RailsやLaravelなどのフレームワークで認証機能を付与する場合セッション管理が一般的であるが、これらフレームワークを完全にAPIとしてのみ使用し、JWTなどの認証トークンを生成しそれをレスポンスとしてフロント側に返却する方法こそRESTfulなAPIのあるべき姿となる。JWTによるステートレスな認証設計は、認証トークンをクライアント側のどこに保存しておくかでセキュリティ面での問題が浮上する。具体的にはXSSやCSRFのような対策が不十分になりかねないという点である。認証トークン(JWT)の管理サーバー側で生成した認証トークンをフロント側のどこに保存するか。認証トークンによる認証フローは、ログイン時、フロント側から送られてきたフォームデータを元にサーバー側のDBからユーザーデータを取得し、認証が成功したらトークンを生成しフロント側へ返却するというもの。JWTの場合、このトークンは元のユーザーデータとハッシュ化のためにサーバー側でのみ保有しているシークレット(文字列)をあーだーこーだしてBase64で変換したものである。シークレットによるハッシュ化をしているため、改ざん検知はできるものの、あくまでBase64でエンコードしているだけなのでデコードすればすぐユーザー情報がわかってしまう、そのような仕組みである。このトークンは外部に漏れてはならない個人情報と同等のものとして扱わなければならない。そのトークンをどこに保存するか、それが今回の問題提起である。懸念すべき事項はXSSとCSRFの対策。ネットで調べると「LocalStorage」または「Cookie」に保存する方法を載せた記事が複数ヒットする。LocalStorageへの保存ローカルストレージに保存する場合、サーバー側はHTTPレスポンスのAuthorizationヘッダーにトークンを入れる。Authorizationヘッダーでトークンを送信した場合、「AuthorizationヘッダーにJWTが入っている=自分で正規に送ったリクエスト」となるため、CSRFの対策となる。Ajaxやaxiosによるサーバーへの非同期HTTP通信はクロスドメイン(CSRFの罠サイトから自分のAppサーバー)では行えず、またHTMLのformタグによるHTTP通信はAuthorizationヘッダを変更することができないためである。localstorageはクライアント側のJSで簡単にアクセスできるのでXSS時にトークンが奪われる危険性がある。Cookieの利用まず、HTTPヘッダーにおいて、Cookieはどのように取り扱われているかをまず知る必要があった。そもそもHTTPヘッダーには種別が存在する。リクエストヘッダーとレスポンスヘッダーである。名前の通り、リクエストヘッダーはHTTPリクエスト時に利用され、レスポンスヘッダーはHTTPレスポンス時に使用される。Cookieを取り扱う場合、リクエストとレスポンスでヘッダー名が異なる。クライアントからサーバーへのリクエスト時は「Cookie」ヘッダー、サーバーからクライアントへのレスポンス時は「Set-Cookie」ヘッダーを使用する。上はMDNのスクショ。MDNSet-Cookieヘッダーはセキュリティ対策のための様々な属性を付与することができるので、サーバーからのHTTPレスポンスについてはXSS対策やCSRF対策を行うことができる。具体的には、secure属性、httpOnly属性、SameSite属性などが該当し、secure属性でhttps通信のとき飲みリクエストでCookieを送れ、httpOnly属性をつけることでクライアント側のJSからアクセスできないようにできるのでXSS対策となる。SameSite属性によってクロスドメインではCookieがリクエストに乗らなくなり、ログインなしで匿名で書き込みができる動的サイト以外はCSRF対策が行える。一方で、Cookieはサーバーからのレスポンスで作られるタイミング以外に、クライアント側JSで保存することも可能である。この手法でCookieを作成した場合、上記のhttpOnly属性が付与できない。Authorizationヘッダーで認証トークンをクライアント側へ送り、単なる保存場所としてステートレスに、クライアント側のJSでCookieを保存する方法は宜しくないということである。僕が調べた限りでは、認証はセッション管理を行い、セッションIDの入ったCookieとCSRFトークンの2つを、クライアント・サーバー間でやり取りするフレームワークが使用している方法が、XSSとCSRF対策の観点で言えばやっぱり良いのであるなという感想。APIファーストにする場合はどうするべきなんだ、ということだが、とりあえずTwitterの認証はどうなってるのかを見ると、Cookieに「auth_token」が入っている。すべてのリクエストで送信されるため、特にモバイルデータ通信で効率を悪くする可能性があるともMDNに書いてあったが、毎回裏で認証作業をするトークンはトークンで、素人目に見て効率は悪い気もする。認証に関しては、ステートレスを重んじるよりも、状態を保持して管理することでセキュリティ的にも良いのではないかというのが今回の自分の中での結論。これからも気にしていきたい。富士通ラーニングメディアRESTアンチパターンまとめ参考サイトはどれも面白かったので見るとためになると思う。以上ありがとうございました。コメントをキャンセルメールアドレスが公開されることはありません。コメント名前メールアドレスサイト上に表示された文字を入力してください。参考にしたQiitaストックの振り返り後編。Pythonオンライン学習教材として評判のPyQ(https://pyq.jp/)によるPython学習記録です。Pythonオンライン学習教材として評判のPyQ(https://pyq.jp/)によるPython学習記録です。データをJSONファイルで管理してそのデータをfor文で回したいと思い、調べたらgulpでejsテンプレートエンジンを動かす方法が出てきた。