developers

BFF (Backend for Frontend) パターン

BFF (Backend for Frontend) アーキテクチャパターンを使用して、トークンをより安全に保つ方法を学びましょう。

本ブログ記事は 2024年4月29日 に公開された「The Backend for Frontend Pattern」を機械翻訳した記事となります。

OAuth 2.0 と OpenID Connect を使用するアプリケーションにおける最大の課題の一つは、トークンのセキュリティです。ID トークン、アクセストークン、リフレッシュトークンを悪用を防ぐことは、これらのプロトコルの優先事項です。プロトコルではいくつかの技術的解決策を採用しており、いくつかのベストプラクティスが提案されています。この記事では、その一つである BFF (Backend for Frontend) パターンの理論的背景とアーキテクチャについて解説します。

OAuth 2.0 クライアントのタイプとセキュリティ

OAuth 2.0 ではクライアントアプリケーションを機密クライアントと公開クライアントの 2 種類に分類します。この分類は、クレデンシャルを安全に保管できるアプリケーションかどうかに基づいており、RFC に定められています。:

  • 機密クライアント (Confidential Client): クレデンシャルの機密性を維持する能力があるクライアント(例えば、クレデンシャルへのアクセスが制限されている安全なサーバー上に実装されたクライアント)、あるいは他の手段を使用して安全にクライアント認証が可能なクライアント。
  • 公開クライアント (Public Client): クレデンシャルの機密性を維持できないクライアント(例えば、リソース所有者が使用するデバイス上で実行されるクライアント、例えばユーザーのスマホにインストールされたネイティブアプリケーションや Web ブラウザ上で動作するアプリケーション)、および他の手段を使用して安全にクライアント認証が行えないクライアント。

つまり、機密クライアントはサーバーのような保護された環境で実行されるアプリケーションを指し、公開クライアントとはユーザーのデバイスのような制御されていない環境で実行されるアプリケーションを指します。一般的な機密クライアントは従来からの一般的な Web アプリケーションであり、一般的な公開クライアントはシングルページアプリケーション (SPA)、デスクトップおよびモバイルアプリケーションです。

公開クライアントはそのクレデンシャルを安全に保管できないため、トークンが不正使用されるリスクがあります。認証できないクライアント(つまり、公開クライアント)にトークンを発行する問題を軽減するために、特定のフローが設計されています。PKCE を使用した認可コードフローです。とはいえ、公開クライアントのセキュリティに関するすべての懸念が解決されるわけではありません。

SPA のセキュリティとトークン

公開クライアントにとってのもう一つの懸念はトークンの保存です。サーバーから ID トークンまたはアクセストークンを受け取った後、公開クライアントはどうすれば不正アクセスからトークンを保護できるでしょうか?アプリケーションの実行中は、アプリケーションのメモリがトークンを保存する最も安全な場所と言えます。しかし、アプリケーションが一時的に停止した場合はどうでしょうか?

例えば、トークンをメモリに保存する SPA について考えてみましょう。ユーザーがアプリケーションが実行されているページを更新した場合、どうなりますか?トークンが失われ、アプリケーションが新しいトークンを取得するために、ユーザーは再度認証を強いられます。

ブラウザのローカルストレージや Cookie にトークンを保存し、リフレッシュ後にリロードすることも考えられますが、これはあまり推奨できる方法ではありません。悪意のあるユーザーやアプリケーションがローカルストレージやCookie にアクセスし、トークンを取得する可能性があります。ブラウザストレージの使用に関するリスクの詳細については、この記事 を参照してください 。

ネイティブアプリケーション(デスクトップやモバイル)でも同様のことが起こりえます。ユーザーがアプリケーションを閉じると、すべてのトークンが失われ、次回アプリケーションを開いたときに認証が必要になります。これは一部の状況では許容されますが、特にモバイル環境では多くのユーザーはアプリケーションを開くたびに認証する必要がないことに慣れています。ただし、ネイティブアプリケーションは SPA よりも有利な点があり、iOS/Mac の Keychain や Android の Keystore など、ほとんどの OS には安全なストレージメカニズムが存在します。

アプリケーションに求められるセキュリティレベルは、アプリケーションの提供する機能によって異なります。これはデータの機密性要件と実装の容易さのバランスを取った結果と言えるでしょう。したがって、場合によっては PKCE を使用した認可コードフローや SPA でのトークン管理に関するすべての懸念事項が許容されることもあります。一方で、許容されない場合もあります。トークンが不正な第三者の手に渡るリスクを絶対に避けたい状況では、どのような対策を講じることができるでしょうか?

BFF (Backend for Frontend) パターン

OAuth 2.0 仕様のクライアント定義にあるわずかな記載が、解決策を示唆しています。

A client may be implemented as a distributed set of components, each with a different client type and security context (e.g., a distributed client with both a confidential server-based component and a public browser-based component). クライアントは分散コンポーネントのセットとして実装され、それぞれが異なるクライアントタイプとセキュリティコンテキストを持つ場合がある(例:機密性の高いサーバーベースのコンポーネントと公開のブラウザベースのコンポーネントの両方を持つ分散クライアント)。

これにより、よく知られているアーキテクチャである BFF (Backend for Frontend) パターンの採用が検討されるようになります。これは、専用のバックエンドを構築してクライアントプラットフォームの特定のニーズに対応する人気のあるパターンです。このパターンが求められる理由は様々で、関心事の分離、柔軟性と保守性、最適化されたパフォーマンス、セキュリティなどが挙げられます。

こちらを読んで、このアーキテクチャパターンがどのように生まれたかを学んでください。

OIDC や OAuth 2.0 を利用する際の SPA のセキュリティ強化は重要であり、BFF パターン利用時のガイドラインが OAuth 2.0 のベストプラクティスで提供されています。

BFF パターンがどのように機能するか見て行きましょう。

BFF のアーキテクチャ

SPA が PKCE を使用する認可コードフローを利用する際の一般的なシナリオでは、以下の図のようなシステムが登場します。:

OIDC や OAuth 2.0 を使用する SPA のアーキテクチャ

SPA は認可サーバー (Authorization server) と通信して ID トークン、アクセストークン、リフレッシュトークンを取得します。その後、SPA は ID トークンからユーザーに関する情報を取得し、アクセストークンを使用して API を呼び出し、リフレッシュトークンを使用してアクセストークンの有効期限が切れた際に新しいトークンを取得します。

このシナリオでは、すべてのトークンが SPA によって保持されます。

BFF パターンを適用して、以下のように専用バックエンドという新しいコンポーネントを構成に追加する構成が考えられます。:

BFF architecture

この構成では、バックエンドは3つの主要な役割を担います。:

  • 認可サーバーとのやり取りは機密クライアントとして行います。
  • SPA に代わってすべてのトークンを管理し、SPA はトークンにアクセスできなくなります。
  • API へのすべてのリクエストをプロキシし、転送前にアクセストークンを埋め込みます。

SPA はバックエンドとのみ通信し、認証されたセッションには従来の Cookie を利用します。バックエンドは、SPA がユーザーとリモートの API にアクセスできるよう、基本的に複数のエンドポイントを公開します。

このアーキテクチャはトークンを扱うセキュリティを向上させる事ができます。なぜなら、この構成ではトークンの取得は機密クライアントとしてサーバー側で行われ、トークンの保存含めセキュリティを向上できるからです。

BFF のフロー

以下の図を参考にして、アーキテクチャ内のシステム間のやり取りを見てみましょう。:

BFF のフロー

各ステップの説明は以下の通りです。:

  1. SPA はバックエンドの URL(上記の例では
    /login
    )を指定して、ユーザー認証を開始します。
  2. バックエンドは、通常の OpenID Connect 認証フローを認可サーバーと開始します。ユーザーを認証して必要なトークンを取得するために、バックエンドはユーザーブラウザを認可エンドポイントにリダイレクトします。
  3. 認可サーバーはバックエンドにトークンを発行し、バックエンドはトークンをキャッシュに保存します。
  4. バックエンドはユーザーセッションを表す Cookie を発行し、ブラウザ上の SPA で再読み込みをトリガーします。
  5. SPA が Cookie を含む API リクエストをバックエンドに送信します。
  6. バックエンドは SPA からのリクエストを検証し、セッションに紐づけられているアクセストークンを取得し、トークンを使用して API への呼び出しを行います。
  7. API がバックエンドにレスポンスを返します。
  8. バックエンドがレスポンスを SPA に転送します。

注意点とバリエーション

この構成の BFF パターンは、OIDC/OAuth 2.0 のフローで SPA を使用する際の主な懸念事項を解決します。:

  • トークンに関するやり取りを公開クライアントが行わず、トークンのやり取りは機密クライアントであるバックエンドの役割となります。
  • SPA はページの更新後にユーザーの再認証を防ぐために、トークンを保存する必要がなくなりました。

この構成の BFF を適切に実装するには、以下の点に注意する必要があります。:

  • バックエンドが発行するセッション Cookie には、
    Secure
    HttpOnly
    のフラグを付ける必要があります。
  • Cookie は、認可サーバーによって発行されたトークンと関連付けられる必要があります。
  • トークンはサーバーサイドまたはセッション Cookie に保存できます。後者の場合、Cookie は暗号化する必要があります。
  • バックエンドは、CSRF (Cross-Site Request Forgeries) 攻撃を防ぐためのすべての対策を実装する必要があります。
  • BFF パターンは、JavaScript から直接保護された API を呼び出す単独の SPA には適用できません。SPA は一般的な Web アプリケーションとして実装されるバックエンドと一緒に動作させる必要があります。

この BFF パターンは SPA のトークン交換と保存に関する主要なセキュリティ懸念を解決しますが、一部の開発者はパフォーマンス問題を懸念しています。具体的には、API のプロキシのようにバックエンドが行う仲介が、いくつかの状況ではボトルネックになる可能性があります。このニーズに対応できる BFF パターンの代替方法もありますが、セキュリティの低下を伴います。

代替案の 1 つは トークン仲介バックエンド パターンで、BFF パターンと同様にバックエンドがトークンの取得を行いますが、SPA にアクセストークンを提供します。これにより、SPA はアクセストークンを使用して保護されたAPI を直接呼び出すことができます。このパターンでは、バックエンドを信頼できる機密クライアントとして利用することでトークンを取得するやり取りを安全にしますが、アクセストークンの保存と保護の問題は未解決のままです。この問題を解決するために、OAuth 2.0 に DPoP を使用することを検討してください。これにより、アクセストークンがクライアントに紐付けられ、所有しているだけで使えるベアラートークンではなくなります。もちろん、この場合には DPoP に対応した認可サーバーが必要となります。

サマリー

トークン管理と SPA に関連する懸念について、より深い理解が得られたでしょうか。SPA は、他の公開クライアントと同様に ID トークン、アクセストークン、リフレッシュトークンを必ずしも安全に扱える環境ではないということを説明しました。もちろん、懸念に対処する必要があるかは、アプリケーションの特有のビジネス要件に強く依存します。

もし SPA により安全なアーキテクチャが必要な場合は、BFF パターンを適用することが 1 つの方法です。このパターンでは SPA に代わってトークンのやり取りと管理を行う専用のバックエンドを導入します。これにより、トークンが SPA に到達することはなく、バックエンドが保護された API へのリクエストのプロキシも担当します。