Open2
nest: service workerを使用したトークンの保護

nestはGitHubのような外部サービスから実際のアプリケーションコードを読み込むことになるので、外部サービスのトークンをJavaScript的に処理する必要がある。で、nestならではの特殊な事情として、WebRTCを張りっぱなしにする必要から、 認証のためにアプリの画面遷移を行うことはできない という制約がある。
そこで、
- アプリのメイン画面
- 認証フローの画面
のタブを分け、それを Service workerを間に挟んで通信させることにした。認証フローが完了したらService workerにメッセージをpostしてトークンを渡す。
更に、この構成にすることでトークンの実値をメイン画面に渡さないデザインを実現できる。Service workerは全ての外部アクセスを処理できるので、トークンの付与もそこで行えば良い。ただし、Amazon S3のようにリクエスト自体の署名が必要なケースではService worker側もそのアルゴリズムを知っている必要がある。

ChatGPTに書かせたコード
正しそう。誤送信防止のためのURLフィルタリングが追加で必要などアドバイスもかなり適切だった。
コードはIndexedDBからtokenを取る仕様になっているが、これは当然on memoryでよい。
// sw.js
self.addEventListener('fetch', (event) => {
const req = event.request;
// 例: ページが付けた独自ヘッダが "Apply-Bearer-Token: true" のときだけ適用
const wantsBearer = req.headers.get('Apply-Bearer-Token') === 'true';
// no-cors はブラウザ仕様で「非シンプル」ヘッダを追加できないので除外
const canModifyHeaders = req.mode !== 'no-cors';
// 例: 同一オリジンの API にだけ付ける(漏えい防止)
const sameOrigin = new URL(req.url).origin === self.location.origin;
if (wantsBearer && canModifyHeaders && sameOrigin) {
event.respondWith(addAuthAndForward(req));
}
});
async function addAuthAndForward(originalRequest) {
// ここでトークンを取得(IndexedDB、Memory、postMessage で受け取る等)
const token = await getTokenFromIndexedDB(); // 実装は適宜
// Request のヘッダはイミュータブルなので一度コピーしてから編集
const headers = new Headers(originalRequest.headers);
headers.delete('Apply-Bearer-Token');
headers.set('Authorization', `Bearer ${token}`);
// 既存の Request を入力にして新しい Request を作ると、
// method/body/各種フラグが引き継がれる
const newRequest = new Request(originalRequest, { headers });
// そのままネットワークへ
return fetch(newRequest);
}