Cookie認証とToken認証
2022/08/10
まず前提として、現時点で完全にsecureな認証方法は存在しません。 XSSなどで外部から自由にJSが実行できる状況であれば、どの認証方法でも突破される可能性があります。
重要なのは各認証方式それぞれの特性を理解し、認証リスクをどこまで許容するかを判断した上で、採用する認証方式を決定することだと考えます。
とはいっても、認証は奥が深いものなのでリスクを判断することが難しいことも多々あります。 そこで、個人的な認証に関する知識の整理も兼ねて、もう一度主要な認証方式について調べ直してみました。
ここでは最初に、現在主要なCookie認証とToken認証について紹介し、最後に現時点で個人的にベストだと思う認証方式を紹介します。
Cookie認証
概要
Cookie認証の大まかなフローは以下のとおりになります。
- browser側から認証リクエスト
- server側でcookie生成、headerの
set-cookie
にcookieをつけてresponseを返す - browser側はrequest先のドメインと紐付けて受け取ったcookieを保存
- browser側で対象のドメインにrequestを出す際に、保存したcookieを付与
- server側で付与されたcookieをもとに認証
注意点としてCookieを使用する場合は基本的にhttpOnly属性をつける必要があります。 httpOnly属性をつけない場合、JSからCookieの中身を取得できてしまうので、token認証で紹介するXSSによる秘匿情報取得のリスクを大きく許容することになってしまいます。
CSRF(Cross Site Request Forgery)リスク
httpOnly属性を持つCookieはXSSによる情報漏洩のリスクが小さいですが、一方でCSRFのリスクが存在します。
CSRFとはCookieがbrowserからのリクエストに自動で付与する特性を利用して、認証が必要な操作を外部から強制的に実行する行為です。
具体的な例を一つあげると、
- 攻擊者は罠となるwebサイトを用意
- 会員制のSNSサイトにログインしているユーザーに対して、上記のURLを踏ませるような仕掛けを実行する(公開掲示板、SNS、フィッシングメールなど)
- SNSにログインした状態のユーザーが該当のURLを踏むことで、攻擊者が仕込んだスクリプトが実行され、SNSへ悪意のある投稿リクエスト(殺害予告など)が送信される
- CSRF対策をしていないSNSサイトはCookieが付与されたリクエストを正常なものとして受け取り、処理する
などがあります。
近年のbrowserではドメインをまたぐリクエストはデフォルトでCookieを付与しない仕様になったので、ドメインまたぎを許可していない限り、このような怪しいwebリンクからCSRFを受けるということは起きにくくなりました。
ただし、Cookieのcross domainをあえて許可している場合は例外なので独自に対策が必要です。
また、同じドメインからであれば認証が必要な操作をユーザーになりすまして実行することができるので、完全にCSRFを防げるというわけではありません。
そこで、CSRFの有効な対策手段としてCSRFトークンを利用する方法があります。
CSRFトークンは画面ごとで発行されるワンタイムトークンであり、 Cookieとは別でワンタイムトークンをリクエストに含めることで本人からのリクエストであることをserver側は確認することができます。
ただし、CSRFトークンの実装はその性質上、フロントとバックエンドがそれぞれ独立しているSPA構成では実現することが難しいという欠点があります(一般的にCSRFトークンを実装する場合はサーバサイドでhtmlにtoken情報を埋め込む場合が多いが、SPA構成ではできない)。 そのため、SPA構成ではCookie認証よりも後述のToken認証の方が使われる場合が多いです。
Token認証
概要
token認証の大まかなフローは次のとおりです。
基本的にはCookie認証と似たようなフローで、
- browser側から認証リクエスト
- server側でtoken生成、bodyにtokenをつけてresponseを返す
- browser側で受け取ったtokenを保存
- browser側でrequestを出すたびに、headerにtokenを記載
- server側でrequest headerのtoken情報をもとに認証
となります。
tokenの保存場所としてはlocalStorage
が採用される場合が多いです。
jsで簡単に保存、取得ができるのですぐに実装できる利点がありますが、一方で、↓で紹介するXSSによってtoken情報が抜き取られやすいというリスクがあります。
XSS(Cross Site Scripting)リスク
XSSとはユーザーがWebページにアクセスすることで不正なスクリプトが実行されてしまう脆弱性または攻撃手法のことです。
具体的な例としては、動的Webページの表示内容生成処理の際にWebページに任意のスクリプトが紛れ込んでしまい、Webサイトを閲覧したユーザーの環境でそのスクリプトが実行されてしまうケースなどがあります。
XSSへの対策としてはサニタイジングがあります。 サニタイジングとはHTMLとして意味のある文字を別の文字列に置き換える処理のことです。 HTMLでは「<」や「>」がタグの一部としてブラウザに認識されます。 そこで、これらを「<」や「>」といった文字列に置き換え、タグとしてではなくただの文字列としてブラウザ上に表示させる方法になります。
最近はReactやVueなどがこの処理をデフォルトで行っているので、明示的に許可をしていない限り、XSSによるscriptの実行は行われないようになっています。
現状のベストプラクティス(tokenのin-memory保存方式)
ここまでCookie認証とToken認証、それぞれの概要とリスクを紹介してきました。
気になるのは、どれを使えばいいかということですが、
SPAでの現状ベストな認証方式はjwt token
をin-memoryに保存する方式だと思われます。
有名な認証サービスの一つであるauth0でも同様の方式が取られているようです。
参考: https://auth0.com/blog/jp-refresh-tokens-what-are-they-and-when-to-use-them/
in-memory
保存を採用するために、この方式では2つのToken、access token
とrefresh token
が存在し、access token
が従来のjwt token
と同じ役割、refresh token
がそのaccess token
を発行するための役割を担っています。
この認証方式の大雑把なフローは以下のとおりです。
- パスワードなどで認証サーバより
refresh token
が保存されたCookieをもらう - clientはその
refresh token
をもとに認証サーバよりaccess token
をもらう - clientはその
access token
を用いてToken認証でサーバとやり取りを行う memory
からaccess token
が消えたり、access token
の有効期限が切れた際にはその都度refresh token
を用いて認証サーバよりaccess token
を発行してもらう
これにより、リソースにアクセスするためのtokenはmemoryに保存できるようになるので、XSSなどによりtokenが抜き取られにくくなります。
またaccess token
の有効期限を短くしておくことで、万が一、tokenが漏れたとしても悪用されにくいという利点もあります。
仕組み上、認証サーバへのアクセスが多くなってしまうので、認証サーバと通常サーバを分けている場合が多いですが、2つのサーバは統一して1つのサーバとして運用することも可能です。
【重要】XSS脆弱性があるサイトではどんな認証方法でも簡単に突破される
認証について簡単に説明してきましたが、一つ知っておくべき重要な事実があります。
それは、
XSSによって任意のスクリプトが実行できるサイトでは、どんな完璧な認証方法を採用していても簡単に突破される
ということです。
よくCookie認証はXSSリスクがなくて、Token認証はXSSリスクがあると議論されますが、 それはCookieやlocalStorageに保存されている情報がXSSによって取得されやすいかどうか程度の話であり、 XSSによって悪意のあるリクエストが処理されるリスクはCookie認証もToken認証と同じくらいあります。
XSSによるリクエストは同ドメインである、かつ、リクエストに使用するトークン(CSRF token
、jwt token
など)も任意のXSSが可能であれば簡単に取得できるからです。
攻撃者が本気を出せば、XSS脆弱性がある時点でどんな認証も突破することは容易なので、 CSRF対策をした、tokenの保存先をin−memory方式にしたからといって、securityに関して過度な期待をすることは禁物です。
※ 攻撃者は基本的に汎用性のある特定のスクリプトを実行させる場合が多いので、「その特定のスクリプトによる認証突破を防ぐ」という意味ではtokenのin-memory保存対策などは一定の効果があります。
おわり
個人的にはXSS脆弱性がある時点でどの方式でもセキュリティリスクはだいたい同じなので、localStorage方式を過度に忌避する必要はないと思っています。