はじめまして、グッドパッチでサーバーサイドエンジニアをやってる荒木です。
この記事は Goodpatch Advent Calendar 2025 の 12日目の記事になります。
はじめに
Webサービスで「ユーザーごとに安全にファイルをダウンロードさせたい」「リンクが流出してもアクセスされないようにしたい」という要件は非常に多いです。
この記事では、認証付きファイルダウンロードを実現するための代表的な方式を整理し、それぞれのメリット・デメリットを考えてみます
認証付きファイルダウンロード方式の代表パターン
パターン1:セッションCookie方式 / Bearer Token方式
[ブラウザ] ---(Cookie付きリクエスト)---> [サーバー] ---> [ファイル返却]
メリット
- 実装が非常にシンプル
- 既存の認証基盤をそのまま利用可能
デメリット
- サーバー負荷が高い:すべてのダウンロードがアプリケーションサーバーを経由
- CDN との相性が悪い:認証が必要なためキャッシュできない
(※今回は前提としてユーザー毎なので問題無し)
向いている用途
- 小型〜中型ファイル
- ダウンロード頻度が低いシステム
パターン2:署名付きURL方式
[サーバー](認証済ユーザーにURL発行)→ [ブラウザ] → [S3などのストレージに直接アクセス]
メリット
- Webアプリの負荷が軽い
- 高スケーラビリティ:ダウンロード自体はストレージサービスが処理
- URLの有効期限・IP制限などができる
デメリット
- URLが漏えいしたら有効期限内はアクセスされる
- ダウンロード前にユーザーの認証情報を再確認できない
- クラウドサービス依存
向いている用途
- 大規模サービス
- 大容量ファイルのダウンロード
- 高スループットが必要なシステム
パターン3:ワンタイムトークン付きダウンロードリンク
① [ブラウザ] → [サーバー](トークン発行)
② [ブラウザ] → ダウンロードURL?token=xxxx
③ [サーバー] → 公開前に token を検証 → ファイル返却
メリット
- URL流出リスクが低い(即時無効化可能)
- 厳密なアクセス制御:1回使ったら無効になる
- ダウンロード通知・ログ取りがやりやすい
デメリット
- トークン管理テーブルの肥大化(定期的なクリーンアップが必要)
- サーバー経由でのダウンロードになるため、大容量ファイルは負荷が大きい
向いている用途
- 機密性の高いファイル
- ダウンロードをイベントとして扱いたい場合(有料ダウンロードなど)
- 監査要件があるシステム
パターン4:プロキシ方式
[クライアント] ---> [サーバー](nginx) --(auth_request)--> [認証API]
|
+---(認証OK)---> [ファイルストレージ/S3]ブラウザ/クライアント
メリット
- アプリケーションコードがシンプル
- nginx が効率的にファイル転送を処理
デメリット
- インフラ設定が複雑になる
- nginx の設定スキルが必要
向いている用途
- 既に nginx を使用しているシステム
- 内部ストレージからの配信
方式別比較表
| 方式 | セキュリティ | 実装難易度 | スケーラビリティ |
|---|---|---|---|
| セッションCookie方式 | ◎ | 低 | △ |
| 署名付きURL方式 | △ | 中 | ◎ |
| ワンタイムトークン方式 | ◎ | 中 | △ |
| プロキシ方式 | ◎ | 中 | ○ |
補足
- URL流出のリスクが許容できるかどうかが重視してます
どの方式を選ぶべきか?
-
大容量ファイルに大量アクセス → 署名付きURL
-
セキュリティ最優先したい → ワンタイムトークン
-
アプリで細かい制御をしたい → セッションCookie方式
-
インフラチームが揃ってます! → プロキシ方式
実際のプロダクトに導入を検討するとしたら?
1. 認証方式の選択
既存サービスでは認証に JWT(JSON Web Token)を採用しており、サーバー側でセッション状態を保持する必要がない。そのため、従来の セッション Cookie 方式 ではなく Bearer Token 方式 を利用することは技術的に適合する。
しかし、Bearer Token を利用して認証付きダウンロードを行う場合、フロントエンド側では以下の対応が必要になる。
- リクエストに Authorization ヘッダを付与する
- ブラウザでダウンロードを行うためにBlobを生成し、疑似的にダウンロードをトリガーする必要がある
これは実装が複雑化しやすいため、よりシンプルにリンク操作のみでダウンロードを完結できる ワンタイムトークン方式 を採用することにする。
採用理由まとめ
| 方式 | 利点 | 課題 | 結論 |
|---|---|---|---|
| Cookie セッション方式 | ブラウザの通常ダウンロードが簡単 | サーバーセッション管理が必要。JWTベースの現構成と非整合 | 不採用 |
| Bearer Token 方式 | 現行JWTと整合性が高い | フロントで Blob 対応必須で実装が複雑 | 不採用 |
| ワンタイムトークン方式 | 署名付きURLのようにリンクだけで完結。実装シンプル | サーバー側でワンタイムトークン管理が必要 | 採用 |
2. アーキテクチャ / インフラ構成の検討
ワンタイムトークン方式では、ダウンロードごとにワンタイムトークンの発行および検証が必要となるため、既存サービスに負荷が集中する可能性がある。
そのため、影響範囲を限定する目的で 既存サービスとは独立した(疎結合な)環境 を構築する方向で考える。
独立サービスを採用する理由
- ワンタイムトークン発行APIは頻繁にアクセスされるため、負荷分散しやすい構成を取りたい
- ダウンロード対象が大きい場合、I/O の負荷が本体サービスに波及するのを避けたい
- 将来的にダウンロード専用スケール戦略を取れるようにしたい
3. サービス間通信方式の選定
独立環境を構築する場合、既存サービスと新ダウンロードサービス間の通信方式を選定する必要がある。
候補は以下の 3 種:
| 方式 | 特徴 | 評価 |
|---|---|---|
| gRPC | 高速・型安全 | 新規導入コストが高い。双方向通信までは不要 |
| SQS | 非同期処理に向く | 即レスが必要な場合に同期APIが必要 |
| REST | 現行サービスと同じ方式。導入コストが小さい | 今回の要件に十分 |
今回の要件は「ワンタイムトークンを同期的に発行するだけ」であり、メッセージング基盤やバイナリRPCのメリットがさほど大きくない。
→ 導入コスト・接続性・現構成との整合性から REST を選択。
最終的な構成方針(まとめ)
-
認証方式はワンタイムトークン方式を採用
- JWT セッションレス環境と整合性が良い
- フロント実装が最もシンプル
-
既存サービスとは独立したダウンロード環境を構築
- 負荷分散しやすく、障害や負荷が本体に波及しにくい
- 将来のスケール戦略にも柔軟
-
サービス間通信は REST を採用
- 導入コストと保守性のバランスが最適
- 現サービスとスムーズに統合できる
おわりに
要件に応じて適切な方式を選択し、セキュリティ対策も忘れずに実装しましょう!