🎃

フロント⇔BFF⇔バックエンドでJWTはどこで使うのが良い?

2024/01/29に公開

はじめに

JWT(JSON Web Token)は以下の利点があり、広く使われています。

  1. 軽量かつ効率的: JWT は Base64 でエンコードされ、コンパクトで軽量な形式を持っています。これにより、データを効率的に転送できます。
  2. JWT から値を取り出せる: JWT は、必要な情報がトークン自体に含まれているため、トークンを受信したサーバーは別途データベースや参照元にアクセスする必要がありません。これにより、トークンの検証が迅速かつ簡単に行えます。
  3. 標準化されたフォーマット: JWT は、JSON 形式でデータを表現する標準化された仕様であり、これにより相互運用性が向上し、異なるシステムやプログラミング言語間で簡単に使用できます。
  4. 拡張性: JWT は、標準のヘッダーとペイロードに加えて、拡張可能なクレームをサポートしています。これにより、特定の要件に合わせてトークンの機能を拡張することができます。
  5. セキュリティ: JWT に署名を付与してトークンの整合性を確保できます。署名が正しく検証されない場合、トークンは無効とみなされます。これにより、改ざんや不正なアクセスを防ぐことができます。
  6. 分散型認証: JWT は分散型アーキテクチャに適しており、複数のサービスやマイクロサービス間でユーザー認証や権限付与などを安全かつ効果的に行うのに役立ちます。
  7. 期限付きアクセス: JWT には有効期限(expiration)を設定できます。これにより、トークンが一定の時間経過後に自動的に無効になり、セキュリティを向上させることができます。

特に 6 番目の利点は、マイクロサービスを使用しているプロジェクトにおいて、JWT の使用を大きく後押しします。
ただ、実際に使って行くと一つの疑問が浮かんできます。
それは、JWT を扱うのは BFF で行うべきなのか、バックエンド側で行うべきなのかということです。
そこで、今回は JWT を扱うのはどこが適切かを見ていきます。
といっても、私自身これが正解だ!とは恐れ多くて言えないので、あくまで個人の考えだということはご認識ください。
それでは見ていきます。

この記事のここだけ!!!

JWT の管理は BFF とバックエンドどちらか一方のみで必ず管理する必要はないけど、JWT でわざわざ設定した値を BFF・バックエンド間でやり取りしたいなら、なるべくバックエンド側に寄せるほうが個人的には良いと思っています。
フロントで扱うのは基本なしです。

過激な私:「JWT はバックエンドのみで扱うべきだ」

過激な私は JWT は全てバックエンド側で 管理をすべきだと言います。
BFF_backend.drawio_square_backend.png
それは今回のようなケースでは生の JWT を使うことはあまりなく 、署名つきの改ざん防止をおこなった状態で使用するためです。
JWT はRFC7519で以下のように記載されていることから分かるように、JWT は暗号化や署名ありきで考える必要があります。

JWTs represent a set of claims as a JSON object that is encoded in a JWS and/or JWE structure.

この暗号化や署名による機能こそが、JWT の改ざんを防ぐための防波堤となっています。
攻撃者は暗号化や署名付与の際に使用した鍵を知りえない限り、解読を行うのが非常に困難です。
以上のように適切な処理を行って、JWT から値を取得した場合、その値は安全である可能性が高いです。
そのことを念頭に置いて、BFF で JWT を扱うとどうなるでしょう。
BFF で JWT の情報を取得すると、BFF 内では JWT 周りの仕組みから改ざんの可能性が低い情報を取得できます。
JWT で取得する値はユーザー情報など、バックエンドとしても欲しい情報が多く含まれています。
そのため、BFF で取得した値をバックエンドに渡したい場面はほぼ確実に生まれます。
しかし、この場合バックエンドに渡る情報は JWT ではありません。
となると、BFF からバックエンドに渡される値は JWT 周りの仕組み以外で改ざんされていないことを担保する必要があります。
BFF_backend_only_backend.png
これが JWT はバックエンドで扱うべきだと考える大きな理由です。
折角暗号化や署名をする手間を行っているのに、肝心なバックエンドとのやり取りで暗号化や署名した情報を渡していないことは、情報を取得する部分でその恩恵を全く受けなくなるので、もはや JWT を使う必要がありません。
そのため、JWT を扱う場合は等しくバックエンドであるべきだと主張します。
ただ、ユーザー情報をフロントに表示させるときなど JWT の情報をバックエンドではなくフロントに渡す場合についてどうするかは考える必要があります。
JWT は必ずしもユーザー情報を含める必要はありませんが、JWT の中にユーザー情報を含まれていることは結構あります。
その際は直接 JWT の値を BFF で取り出し、それをフロントに返すだけで良さそうに思えます。
特に DB にアクセスするわけでもありませんし、バックエンドに通信したとしてもやることはほぼ同じなので通信コストをかけるだけになりそうです。
しかし、過激な私は BFF に JWT を扱うためのロジックが生まれることを極端に嫌うので、上記状況の場合はバックエンド側にユーザー情報を取得するための処理を書き、それに JWT を渡す形で呼び出すことを望みます。
BFF に JWT の処理を行うことを許容してしまうと、実は JWT を渡すべき部分で JWT を渡さず JWT から取り出した値を渡してしまう可能性があります。
JWT の魅力である改ざん防止という利点を、実装が楽になるからという理由だけで危険にさらすのは受け入れがたい気持ちがあります。
それに実装が楽になるという点もそこまで利点に感じていません。
JWT は確かに改ざん防止に耐性はありますが、JWS の場合トークンの中身は確認できるので、JWT に機密情報を持たせることはありません。
なので、JWT のみでフロント側の要求が完了することなく、何かしらの処理を追加で行うことは往々にしてあります。
追加で処理をするなら、最初から JWT をそのままバックエンドに渡すという運用をしても特に工数が追加でかかることはありません。
以上のことから、過激な私は JWT のすべてバックエンドで扱うべきだと思います。

楽観的な私:「その時で気分で JWT を扱えばいい」

楽観的な私は JWT はサーバー側であれば好きに扱えばいいと思っています。
OAuth の IETFで「TLS」とワード検索をかければ、多くの項目で TLS による通信を必須とする文章を見かけます。
JWT の使用=OAuth というわけではありませんが、OAuth はやり取りにトークンを使用します。
OAuth が TLS 必須と言っているように、現在のアプリケーションにおいて TLS 対応してからアプリを公開するのはもはや常識と言えるほどです。
なので、現状 BFF・バックエンドでのやり取りで情報が抜き取られたり、改ざんされたりすることはほぼほぼありえません。
情報のやり取りの安全性が担保されているなら、JWT をどこで扱っても一緒なのでその時都合で JWT から値を取得して良いと思っています。

現実の私:「状況によって、BFF とバックエンドで使いわけよう」

ここまでで、イマジネーションな私を見てきました。
それらの考えを踏まえつつ、現実な私は JWT の扱いを次のように考えます。
まずはバックエンドで使うことを第一に考える。バックエンドだけで扱うのが苦しい時は BFF で扱うようにする。
あるべき姿としては可能な限りバックエンド側に寄せたいと思っています。
JWT による改ざん防止などの機能は魅力的なので、その効果を最大限発揮するために使う直前までそのまま渡したいです。
そして、TLS による通信は確かに安全ですが、100%大丈夫という保証はありません。
なので、仮に情報が抜き取られたとしても、改ざんは防げるようにしたいです。
以上のことから、基本はバックエンド側で JWT を扱うことを第一に考えます。
しかし、バックエンドの種類によっては JWT を受け取ることを考えていないものは往々にしてあります。
そして、そのサービスが自分で作ったものではないと、JWT を受け取るように変更することも困難です。
この場合は、BFF で JWT を処理して値を取得した後、その値で通信を行うのはよしと考えます。
まず現実的に JWT を渡せないことと、呼び出すサービスがにとってその情報が改ざんされているかいないかはどってでもいいことだからです。
上記のようなケースがあるので、BFF で JWT を扱うことは避けれません。
そのため、フロント側に JWT の情報を渡すのもバックエンドを介さず、BFF で処理したものを渡しても良いと考えます。
いずれ BFF で JWT を扱う可能性は高いので、バックエンドの情報を必要としないならそのまま BFF で完結させたほうが楽です。
これらのことから、実際の開発では基本バックエンドで JWT を使うのを優先で考えつつ、そのケースを成立させるのが厳しい場合は BFF で JWT を扱うという流れになると思います。

楽観的な私でも、断固として受け入れらないこと: フロント側で JWT の値を検証して、それを使用する

先程まで 3 人の私を見てきました。
そのどれもが共通していることに、JWT の値をサーバー側でしか扱っていないことがあります。
サーバーで JWT を扱うことは暗黙の了解として話を進めていました。
不特定多数がアクセスできてしまうフロント側で JWT の検証を行うということは、検証の仕方であったり、鍵情報の流出につながります。
仮に公開鍵だから流出しても安全だったとしても、その暗号の脆弱性が見つかった場合などの対応が非常に大変です。
また、検証の仕方がセキュリティ的によろしくない実装だったら、そもそも検証をスルーさせるようにもできます。
詳細は以前の記事に譲るとして、フロント側で JWT の処理を行うことは危険をはらむ割には旨みがありません。
よって、よっぽどの事情がない限りフロント側で扱うことはしないのが良さそうです。
よっぽどの事情がない限りというのはわかりましたが、よっぽどの事情でどんな事情やねんと思うのが自然な流れです。
そこで最後に、フロント側でもユーザー情報が含まれている JWT 形式のものを扱ってよい場合について見ていきます。

余談 フロント側でも扱ってよい場合もある JWT: SPA アプリ下での ID トークン

タイトルにもあるようにフロントで扱ってよい場合もある JWT の一つが ID トークンです。
OpenId Connect Core 1.0 の「ID Token Validation」の 6 番目に以下の記載があります。

ID Token を Client と Token Endpoint の間の直接通信により受け取ったならば, トークンの署名確認の代わりに TLS Server の確認を issuer の確認のために利用してもよい (MAY).

乱暴に言い換えれば、TLS 環境下で ID トークンやアクセストークンを発行するためだけのエンドポイントから、ちゃんとトークンをもらえていれば署名の検証はしなくても良いとなります。
以下のようにトークンエンドポイントを使うフローはほぼ全てで、インプリシットフローも OAuth2.1 では廃止していく方向です。
**OAuth 2.0のフローの種類と2つのエンドポイントについて解説 より引用** > OAuth 2.0 のフローの種類と 2 つのエンドポイントについて解説 より引用
そのため、将来的には TLS での通信が担保されていれば、ID トークンの検証を行う必要がなくると推測しています。
よって、改ざんされた ID トークンを受け取る点については OpenID Connect の仕様に従っていれば考える必要がなくなります。
次に、フロント側のまずい実装で値を書き換えてしまう問題について見ていきます。
個人的にこれは少し煮え切らない気持ちがあります。
ID トークンの検証機能を持つライブラリは存在します。
そのライブラリの中身を判断し、書き換えかえが起きないことを確認して使うだけで問題は解消されます。
なので、書き換え問題は適切なライブラリを使えば基本的に問題となりません。
ただ、何かしらの事情があり自前で値の検証を行う場合は、ID トークンのデコードから検証までの間に書き換えがないことを自分で判断する必要があります。
ここだけが懸念として残るので、表題で「よい場合もある」と煮え切らない書き方をしています。
とはいえ、個人的にはauth0-spa-js の jwt.tsの処理をほぼコピペするだけで検証の処理は完成するので、今回の書き換えが起きることは限りなく低い可能性だと思っています。
ちなみに、ID トークンはアクセストークンと違い API に送ることがないので、検証後に書き換えをされてもデータベースから別のユーザー情報が抜き取らてしまうことはまずありません。
このように、ID トークンの場合はフロントで受けとり、処理を行っても問題にはなりにくいです。
ただ、やはりバックエンドで扱えるならそちらのほうがより安全なので、JWT を扱ってよいとしてのはバックエンド側で扱うことが難しいこともある SPA 限定としました。

おわりに

今回は JWT を扱う箇所について考察しました。
JWT を検証するのは、JWT の値を使いたい部分で初めて行うのがよいという立場を文字に起こせたのは良かったです。
ただ、自分の中でモヤモヤは残り続けています。
例えば、バックエンド側で JWT の扱いを行うとして、仮に audience クレームを検証に含める場合、マイクロサービスの特性上、多くのエンドポイントを記載することになり処理の遅延を招く恐れがあります。
正直、それくらいは問題にならないほどのスペックを用意するべきだとは思っても、お金の都合などでできない場合もあるかもしれません。
その時は BFF で寄せる方向に話が行くと思いますが、だったらそもそも JWT を使わなくても良いのでは?とも思います。
などなど本当に自分の主張は合っているか不安はぬぐえないので、引き続き学習を進め多角的な視点で判断して行けるようにします。
ここまで読んでいただきありがとうございました。

Discussion