【REST API】認証トークンをどのように扱うのが良さげか調べたことをまとめる

#142 JWTどこに保存するべきか問題

JWTをどこに保存するかを色々調べていたので、それらについて端的にまとめる。そもそもAPIファーストでフロントとバックが疎結合なアーキテクチャはなぜセッションではなくJWTみたいな認証トークンを使用するのかや、APIのRESTの考え方からもう一度復習することにした。

 

目次

[toc]

 

 

APIはステートレスであるべきというRESTの考え

RESTの考え方の1つに「ステートレス」というものがある。APIは「状態を保持しない」設計にしろということである。そもそもHTTP通信は「ステートレス」であり、それだと色々困ることがあったからセッションというものが登場した。セッションは「ステートフル」であり、「状態を保持する」機能である。ステートフルなセッションは、誰のものか判別するために一意のIDが必要になる。一般的にそのIDを保存する場所がCookieであり、クライアント側で作成される。RESTの思想を第一に考えるのであれば、クライアント・サーバー間のセッション管理は望ましくない。

 

APIファーストのメリットをちゃんと知る

APIファーストなアーキテクチャのメリットは、「APIにすることで、他のシステムやアプリケーションと簡単に統合できる」ことにある。APIとして用意しておけば、適切なリクエストを送ることでどのシステムやアプリであっても使用できる点が最大のメリットとなる。つまり、ステートレスな設計でないと、このメリットを最大限に発揮できず、状態に依存した拡張性の少ないAPIになってしまう。そのため、APIはステートレスであるべきだという前提を知る必要がある。

 

認証におけるステートフルとステートレス

認証には、「セッション管理」による方法と、「認証トークン」による方法がある。前述の通り、セッション管理はステートフルな認証であり、一方で認証トークンの利用の場合はステートレスとなる。つまり、REST 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」ヘッダーを使用する。

CookieヘッダーとSet-cookieヘッダー 上はMDNのスクショ。詳細はHTTP ヘッダー – HTTP | MDN

 

Set-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」が入っている。Cookie はすべてのリクエストで送信されるため、特にモバイルデータ通信で効率を悪くする可能性があるともMDNに書いてあったが、毎回裏で認証作業をするトークンはトークンで、素人目に見て効率は悪い気もする。認証に関しては、ステートレスを重んじるよりも、状態を保持して管理することでセキュリティ的にも良いのではないかというのが今回の自分の中での結論。これからも気にしていきたい。

 

参考

HTTP Cookie – HTTP | MDN

CookieのSameSite属性で「防げるCSRF」と「防げないCSRF」 – まったり技術ブログ

JWTを認証用トークンに使う時に調べたこと – Carpe Diem

HTML5のLocal Storageを使ってはいけない(翻訳)

JWT(JSON Web Token)でCSRF脆弱性を回避できるワケを調べてみた話 – Qiita

時代はAPIファースト!? イマドキの業務システム開発ことはじめ 第1回 なぜ流行っている?「REST API」 ~アプリもインフラ自動化もAPIで!~ : 富士通ラーニングメディア

RESTアンチパターン

 

まとめ

参考サイトはどれも面白かったので見るとためになると思う。以上ありがとうございました。

返信を残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA