🗝️

JWT認証のベストプラクティス 5選

2022/11/20に公開2

はじめに

JWT(JSON Web Token)は、アプリケーションの認証方法として最も一般的に活用されている。ところが、JWTは完璧な認証方法というわけではなく、時と場合によっては思わぬセキュリティ攻撃を受ける可能性が考慮される。

しかし、JWTを正しく使うことでこれらの欠点を未然に回避できる。そこで、今回の記事ではJWTをWEB開発で適切に実装する上での重要なポイントを3つ紹介する。

最も適切なアルゴリズムを選択する

JSON Web Tokenは、Header、Payload、Signatureの3つの部分から構成される。ヘッダーには、トークンの種類と署名アルゴリズムに関する情報が含まれる。

{
   "alg": "HS256",
   "typ": "JWT"
}

署名のアルゴリズムには種類が数多く存在し、それぞれに特徴がある。例えば、以下のようなものが挙げられる。

  • HMAC + SHA256(HS256)アルゴリズム:対称型アルゴリズムと呼ばれる。共有している秘密鍵を使用してメッセージに署名できる。
  • RSASSA-PKCS1-v1_5 + SHA256(RS256)ECDSA + P-256 + SHA256(ES256):非対称型アルゴリズムと言われる。公開鍵と秘密鍵のペアが使われる。

しかし、Mediumの記事「Best Practices for Using JWT」によると、RS256はJWTを実装する上では最も推奨されるアルゴリズムだ。理由は以下の通りである。

  • 様々な言語や実装に幅広く対応できる。
  • 秘密鍵の所有者だけがトークンに署名でき、他の人はその署名を検証するために公開鍵を使える。
  • RS256を使えば、様々な人に有効なトークンを要求できる。
  • 秘密鍵が盗まれた場合、更新された秘密鍵で再度デプロイする必要なしに、RS256でキーローテーションを行うことができる。[1]

セッション認証でJWTを使わない

JWTをセッション認証のデフォルトで使うべきではない。ひとつには、JWTは機能が豊富で範囲が広いため、ライブラリの作者またはユーザーのどちらかが間違いを犯す可能性が高くなるからである。

もうひとつの問題として、JWTは自己完結しており、それらを無効にする中央機関がないため、セッションの終了時にJWTを削除することができないことが考慮される。

さらに、JWTはデータの容量が大きい。クッキーと一緒に使うと、リクエストごとに膨大なデータを送受信することになりかねない。

セッション認証にJWTを使用することは、最初は良いアイデアに思えるかもしれない。しかし、以下の理由からセッション認証でJWTを使うべきではない。

  • クライアントにあらゆる種類のユーザー情報を保存できるから。
  • JWTは署名されているので、JWTに格納した情報を取得するためにデータベースを呼び出す必要がないから。
  • 最終的に水平スケーリングの問題になったときに、中央のデータベースでセッションを調整する必要がないから。

最終的には、アプリケーション用のデータベースをすでに持っている場合は、セッションのためにテーブルを使用し、選択したサーバーサイドフレームワークが提供する通常のセッションを使用すればよい

なぜでしょうか?JWTを使用するにはコストがかかる。サーバーへのリクエストごとに送信され、サーバーサイドのセッションと比較して常に高いコストとなる。

また、HTTPSを使用してJWTを送信することでセキュリティリスクは最小限に抑えられますが、傍受されてデータが解読され、ユーザーのデータが公開される可能性がある

Cookieに安全にJWTを保存しよう

JWTはユーザーのブラウザ内の安全な場所に保存される必要がある。もしそれをローカルストレージ内に保存すると、ページ内のどのスクリプトからもアクセスされることになる。XSS攻撃[3]によって、外部の攻撃者がトークンにアクセスできるようになる可能性があるからだ。

何をするにしても、JWTをローカルストレージ(またはセッションストレージ)に保存しないこと。あなたのページに含まれるサードパーティのスクリプトが侵害された場合、すべてのユーザーのトークンにアクセスできる。

安全性を保つには、常にJWTをhttpOnly Cookie内に保存する必要があります。これは特別な種類のクッキーで、サーバーへのHTTPリクエストの際にのみ送信される。ブラウザで実行されているJavaScriptからは、決してアクセスできない(読み取りと書き込みの両方が可能だ)。

発行者(iss)と受信者(aud)を明確に定義する

JWTで発行者と受信者を定義しておこう。これを習慣づけることで、受信者にとってはトークン管理が非常に楽になる。

{
  "iss":"piumi",
  "exp":1300819380,
  "http://myweb.com/is_root":true
}

例えば、あなたが受信者である場合、JWTを受け入れる前に、関連する当事者によって発行され、あなたのために発行されたことを確認する必要がある。これにより、攻撃者が他人のために指定されたトークンを悪用して自分のデータにアクセスする可能性を軽減できる。

有効期限はできるだけ短めにする

JWTは自己完結型のバイ・バリュー・トークンであり、一度発行されて受信者に配信されると、それを取り消すことは非常に困難だ。そのため、トークンの有効期限はできるだけ短く、長くても数分から数時間程度にしよう。トークンの有効期限を日や月で指定することは避けるべきだ。

時間ベースの主張をするときは、サーバーの時刻がマシンによって微妙に異なることを忘れないように

おわりに

今回の記事では、JWT認証を実装する上での重要なポイントを簡潔に紹介した。

  • 最も適切なアルゴリズムを選択する
  • セッション認証でJWTを使わない
  • Cookieに安全にJWTを保存しよう
  • 発行者と受信者を明確に定義する
  • 有効期限を適切に設定する

本記事が実務でJWT認証を実装するプログラマーの手助けになれば非常に幸いである。

参考サイト

https://blog.bitsrc.io/best-practices-for-using-jwt-df3788433fd3

https://curity.io/resources/learn/jwt-best-practices/

https://blog.logrocket.com/jwt-authentication-best-practices/

脚注
  1. ただし、HS256の場合は再び鍵をデプロイしなければならない。 ↩︎

  2. 詳細はe-wordsを参照。「総当たり攻撃」とも言われる。 ↩︎

  3. 詳細はe-wordsを参照。 ↩︎

GitHubで編集を提案

Discussion

ockeghemockeghem

『サーバーサイドフレームワークが提供する通常のセッションを使用すればよい』とするならば、JWTをローカルストレージやCookie等に保存する必要はないように思いますが、どのような目的でJWTを保存する想定でしょうか?

ritouritou

英語の記事を参考にされているようですが、JWT自体の課題、ベストプラクティスとJWT認証(=認証というよりもセッション管理にJWTを使うだけのことを示していそうですが)におけるベストプラクティスが混在していそうなのが気になりました。

例えばRFCではJWT自体のベストプラクティスが出されています。

https://www.rfc-editor.org/rfc/rfc8725.html

https://qiita.com/ritou/items/71e58fbc0c5605ec61cb

これらの内容はJWT一般的なもので、それがJWTを利用する全てのユースケースに当てはまるものではありません。例えばJWTをセッション管理に利用する際にこのケースでこういうリスクがあるから気をつけろ、BCPはこれを参照せよ、といったアプローチで整理すると良い内容になるのではないかと思います。