アプリケーションの認可処理はどうかけるのが良い?
こんにちは!
株式会社TAIANでバックエンドエンジニアをしています、sekkeyです。
今回は、バックエンド開発における認可処理の設計について紹介したいと思います。
ちなみに、以前は「サービスクラスの骨組みとなる ServiceBase について」という記事を公開しましたので、ぜひこちらもご覧いただければと思います。
そもそも認可とは?
認可とは、「このユーザーが、このリソースに対して、この操作をしてもよいかどうか?」を判断・制御する仕組みです。
例えば、Webアプリで「あるファイルを編集する」場面があるとします。
- Aさんが自分のフォルダを編集する → OK
- BさんがAさんのフォルダを編集しようとする → NG(認可エラー)
この「OK」「NG」を判断するのが認可の役割です。
また、似た概念として認証があるかと思います。
概念 | 意味 | 例 |
---|---|---|
認証(Authentication ) |
「あなたは誰ですか?」を確かめる | ログインしてユーザーを特定する |
認可(Authorization ) |
「あなたはそれをしていい人ですか?」を確かめる | 特定のページやデータにアクセスできるか判断する |
認可の判断材料になるもの
アプリケーションによって認可の条件は様々だと思いますが、代表的な判断材料として以下のようなものが挙げられます。
- ユーザー自身(ID・ロール・権限など)
- リソースの所有者や共有設定
- 時間帯や状態(例:公開期間中のみ編集可)
- リクエストの操作種別(閲覧?更新?削除?)
「データの取得と認可を同時に行う」か「データの取得と認可を別で行う」
ここでは、「あるユーザー(User
)」が「該当のファイル(File
)」を更新する場合を考えます。
(GraphQL
のMutation
のresolver
内で実施するイメージで書いています)
データの取得と認可を同時に行う
ここでは「アクセス可能なfile
の中から該当のid
のものを探す」という形で、データ取得の段階で認可の条件をかけています。
結果がnil
だった場合、「ファイル自体が存在しない」または「ファイルにアクセスする権限を持っていない」のいずれかになります。
class Mutations::Files::Update
...
def resolve(file_id, params)
file = current_context.files
.with_accessible_by_user(current_user.id) # ユーザーがアクセス可能かを判断するscope
.find_by(id: file_id)
raise 'ファイルが見つからないか、ファイルへのアクセス権限がありません。' if file.nil?
file.update!(params)
...
end
end
データの取得と認可を別で行う
こちらは、まずfile_id
で該当のファイルの存在確認だけを行っています。
その後にアプリケーションレベルで、「そのユーザーがそのファイルへのアクセス権を持っているか」を明示的にチェックしています。
class Mutations::Files::Update
...
def resolve(file_id, params)
file = current_context.files.find(file_id) # file_idに該当するファイルが存在しなければActiveRecord::RecordNotFound
raise 'ファイルへのアクセス権限がありません。' if file.accessible_by_user?(current_user.id) # ユーザーがアクセス可能かを判断するメソッド
file.update!(params)
...
end
end
どちらが適切なのか?
結論から言えば、目的によって使い分けるのが良いかと思います。
「データの取得と認可を同時に行う」場合のメリット
-
セキュア
エラーが常に「存在しない」や「アクセスできない」としか返ってこないため、攻撃者に情報を与えない。
たとえばログイン機能で、「このメールアドレスは存在しない」「パスワードが違う」を分けず、同じエラーにするのと同じ考え方。
-
コードがシンプルになることもある
検索条件と認可条件が一致するケースでは、クエリにまとめた方が効率的かつ読みやすいこともある。
「データの取得と認可を別で行う」場合のメリット
-
開発者フレンドリー
エラーの内容を細かく分けられるので、「
folder
が存在しない」のか「アクセス権がない」のか、ログやUIに明示しやすい。 -
認可ロジックを柔軟に表現できる
ユーザーのロールや状態、共有設定など、複雑なビジネスルールをコードとして明示的に記述しやすい。
例えば、ファイルを格納しているフォルダに対しても認可処理をしたい場合、それぞれに別の認可ルールが必要になると想定されます。
その場合、個別に認可処理を記述した方が、認可のロジックの意図が明確になり、仕様変更にも柔軟に対応できそうです。
「ログインが前提」のアプリケーションであれば、攻撃者よりは開発者に対してフレンドリーな設計(「データの取得と認可を別で行う」)を基本方針にしても良さそうかなと思います。
その上で、必要な箇所を「データの取得と認可を同時に行う」で対応していくのが良いかなと考えています。
まとめ
上記で述べたことのまとめです。
観点 | データの取得と認可を同時に行う | データの取得と認可を別で行う |
---|---|---|
エラーメッセージ | 一律 | 詳細に分けられる |
デバッグしやすさ | しにくい | しやすい |
攻撃者への情報の与えやすさ | 与えにくい | 与えやすい |
We are hiring!
TAIANでは、このような開発・技術・思想に向き合い、未来をつくる仲間を一人でも多く探しています。少しでも興味を持っていただいた方は弊社の紹介ページをご覧ください。
Discussion