アプリで使用するAPIの開発を行っています。
開発しているAPIの中には、「画像データの管理」「問題の登録」「ファミリーユーザー(親子アカウント)の連携」など、ユーザーの権限や所有権が複雑に絡み合う機能が多く存在しています。
これまで、データをAPIで「取得(GET)」する際には、リクエスト者がそのデータの作成者本人か、閲覧権限があるかを厳格にチェックし、不正アクセス(盗み見)を防ぐ仕組みを実装していました。
しかし、データを「登録(POST)」、あるいは「更新(PUT)」する処理において、「不正なデータや悪意あるパラメータが送りつけられた際の考慮」が不足しているのに気づきました。
せっかくなので、今回の問題点とAPIセキュリティ強化(リファクタリング)を記録として残します。

考慮漏れの内容
SQLインジェクションのように入力値を文字列結合してくる不正行為については、SQLのパラメータ(バインド変数)を使うといった対策をしています。また、ユーザーIDはアクセストークン化して渡しているので改竄不可となっています。
今回の考慮漏れは登録時の所有者チェックと不正な画像URLの設定です。
ファミリーユーザーのなりすまし(IDの改ざん)
・問題とリスク
アプリで使用する問題を登録、更新する際のAPIにて、リクエストボディ(JSON)の 子ユーザーIDを他人のIDに書き換えてPOSTされると、システム上、全く関係のないユーザー同士が勝手に「家族」として登録されてしまいます。
ユーザーID自体はアクセストークン化して改竄不可にはしていましたが、子ユーザーIDは連番で生データを扱っていました。なので、適当に子ユーザーIDを設定してもそのまま登録されてしまいます。
自身が作成した問題には、子ユーザーのユーザー名を作成者名として表示する仕組みにしていたため、他人が子ユーザーの名前を取得してしまうリスクがありました。
・対策
対策は単純にユーザーIDと子ユーザーの関係性を事前にチェックすることです。このチェックは色んな所で使うので、共通処理化しておきました。
UI側からは全体に公開するモードでは子ユーザーIDを設定しないなどの対策を行っていましたが、しっかりバックエンド側でも対策しないといけませんね。
画像ファイルの隠蔽
・問題とリスク
アプリでは画像をアップロードすることが可能ですが、アップロードした画像自体は難読化しており総当たりによる不正取得には対策しています。なので、非公開の画像が流出するリスクを少なくしていました。

しかし、誰でもアクセス可能な状態で公開していると、なんらかのタイミングに漏洩してしまった際に何も制御ができなくなります。(物理削除すれば見えなくなりますが)
漏洩の原因としてはブラウザ等のツールでアクセス可能なアドレスとしてスクリッピングされ、蓄積、公開される可能性があるようです。(実際に起こりうるのかは分からないけど)

・対策
ということで、img [src]で画像を取得する際に、画像の権限認証用のAPIを呼び出して画像データを取得することにしました。
問題データを取得したタイミングで一括して画像データをダウンロードしてメモリ上に展開することも考えましたが、それだとプレイ開始時のローディングに時間がかかるデメリットがあります。
APIで権限を確認してから画像データを取得する方法であれば、画面に表示する分だけで問題ありません。
あとは、一度取得した画像データの場合はキャッシュ化してしまえば、何度もAPIを呼び出す必要もなくなります。
srcに画像データを渡す際にPipeを介して変換する方法を用いていますが
こちらは推奨されているのかどうなのか良く分からなかったです。
[src]="item.image_url | secureImg | async"
Pipeは日付を「yyyy/mm/dd」形式にしたりと、表示形式を整えたりする際に使うので、権限チェックに使うのは不適かもしれません。
ということで、AIさんの回答はこちら
1. メソッド(関数)をHTML内で呼ぶと「最悪のパフォーマンス」になる
もしHTML(テンプレート)内で [src]="getFullImageUrl(theme.thumbnail_url)" のようにコンポーネントのメソッドを直接呼び出すと、Angularの「変更検知(Change Detection)」が走るたびに、このメソッドが何度も何度も再実行されてしまいます。
画面を少しスクロールした、ボタンをクリックした、タイマーが動いた、といった些細なイベントのたびに毎回メソッドが走り、その中でAPI通信の引き金が引かれたり、不要な処理が繰り返されたりするため、画面がガクついたり、無限ループのような挙動を起こす原因になります。
2. Pipeは「超強力なキャッシュ(純粋性)」を持っている
AngularのPipeは、デフォルトで 「Pure(純粋)パイプ」 として動作します。 Pureパイプは、「渡された引数(画像パス)が変わらない限り、絶対に再実行されない」 という強力な最適化(メモイゼーション)の仕組みを持っています。
つまり、画像パスが /uploads/abc.png から変わらなければ、Angularがどれだけ裏で変更検知を回そうとも、Pipeの中身は最初の1回しか実行されません。HTML内での無駄な処理を完全にシャットアウトできるため、パフォーマンスの観点からPipe一択になります。
3. Pipeは「権限チェック」ではなく、URLの「データ形式変換」を行っている
「Pipeで権限チェックをするのは不適では?」という点に対する本質的な回答は、「Pipe自体は権限チェックをしていない」 ということです。
今回の仕組みを役割ごとに分解すると以下のようになります。
- バックエンド(Express): 権限チェック(Token検証とSQL照合)を行う。
- Pipe(フロント): 生の相対パス(文字列)を、ブラウザが表示可能なセキュアな一時URL(
SafeUrl/blob:)という 「データ形式に変換」 している。
つまり、Pipeの本質である「データ変換(String ──► SafeUrl)」の役割からは一切ブレていません。その変換のプロセス(裏側)で、HttpClientによる非同期通信(| async)をスマートにカプセル化して実行しているだけです。Angularでは、このような非同期のデータ変換のために AsyncPipe と自作パイプを組み合わせる手法は、高度なコンポーネント設計として広く認められています。
💡 まとめ:なぜPipeが良いのか?
- メソッド: HTML内で呼ぶと変更検知のたびに毎回暴走し、高負荷の原因になる。
- Pipe: データの変化を監視し、必要な1回だけ通信・変換を行うため、劇的に軽くなる。
ということで、本質的には「データ形式の変換」を行っているのでPipeの使用は適用であり、性能としても優れているとのことです。勉強になりますね。
※AIの回答なので間違っている可能性もあります。
コメント