TT Lab
はじめる
学ぶ 学習パス コース

セッションとトークン — ブラウザからメッシュまで

トークンをブラウザに置かない方法、BFF

TT Labで続きを見る

一言でいうと

SPAがaccess tokenを直接持つと、そのページで動くスクリプトが1つ汚染されるだけでトークンが外に出てしまいます。BFF(Backend for Frontend)はトークンをサーバーだけに置き、ブラウザーにはHttpOnlyのセッションCookieだけを渡します。盗めるトークンがブラウザーにそもそも存在しないようにする設計です。

なぜ必要なのか

しばらくの間、SPAの標準的な構成はこうでした。ブラウザーが発行者からaccess tokenを受け取ってlocalStorageに入れ、APIを呼ぶたびにAuthorization: Bearerで付けます。サーバーが状態を持たないので楽です。問題は、そのトークンを読める主体がこのページで動くすべてのスクリプトだという点にあります。広告タグ、分析スクリプト、汚染されたnpmパッケージ、XSSの1行など、何であってもトークンを読んで攻撃者のサーバーへ送れますし、そのトークンは攻撃者の手の中で有効期限まで使えます。ユーザーがタブを閉じても意味がありません。

IETFはこの問題を長く磨き上げ、RFC 10017 OAuth 2.0 for Browser-Based Applications(ドラフト名はdraft-ietf-oauth-browser-based-apps)として発行しました。複数の構成を比べたうえで、6.1節にBFFを置いています。要点は1文です。トークンがBFFにしかないので、ブラウザーから持ち出せるトークンがありません。

どう動くのか

BFFは、フロントエンドと同じオリジンに立つ小さなサーバーです。このサーバーがOAuthクライアントの役割をまるごと引き受けます。

브라우저 ── GET /login ─────────────▶ BFF   (state 를 담은 임시 세션 발급)
브라우저 ── 302 ▶ 발급자 /authorize  (로그인)  ── 302 ▶ BFF /callback?code=..
BFF ─────── code + client_secret ──▶ 발급자 /token  → access token
BFF: 토큰을 서버 쪽 세션에 저장, 세션 ID 재발급, HttpOnly 쿠키만 내려줌
브라우저 ── GET /bff/api/me + 쿠키 ─▶ BFF ── Authorization: Bearer ▶ 하류 API

守るべきルールは6.1.3節にまとまっています。

現場での姿

BFFを導入した後で最も多い事故は、「利便性のため」にトークンが再び漏れることです。セッション確認のレスポンスにaccess_tokenを一緒に載せたり、フロントが下流のAPIを直接呼びたいからと、JSが読めるCookieでトークンをもう1つ返したりします。その瞬間、BFFは名前だけになります。逆方向もあります。下流のAPIが「内部ネットワークだから」とトークンなしのリクエストを受け付けると、BFFを飛ばした呼び出しがそのまま通ります。

限界も知って使う必要があります。6.1.4.1節は、悪意あるスクリプトがユーザーのブラウザーを経由してBFFへリクエストを送るクライアントハイジャックは、BFFでも防げないと書いています。BFFがなくすのはトークンの窃取であって、XSSそのものではありません。ユーザーがタブを閉じれば攻撃も終わること、その違いがBFFの価値です。

次のラボですること

1つのPodの中で発行者(8312)・下流API(8311)・BFF(8310)を起動し、ブラウザーの役をcurlで務めてログインの流れをたどります。BFFが返したCookieの属性を点検し、RedisのBFFセッションから本物のトークンを取り出して、ブラウザーが見たレスポンスのどこにもその値がないことを確認します。続いて、BFF経由の呼び出しは200、下流への直接呼び出しは401になるか、CSRFトークンのない書き込みが防がれるか、ログアウト後に古いトークンが下流で無効になるかまで、リクエストで証明します。