【実務・中級編】GraphQL のフィールドレベル認可の実装 – アプリケーションセキュリティ & 安全な開発防御ガイド

GraphQLの「フィールド権限漏れ」を封じ込めろ:XSSを防ぐラストラインの守り方

現場でコードレビューをしていると、GraphQLの導入時に「クエリ全体の認可」だけで満足してしまっている設計にしばしば遭遇する。これがどれほど危険か、君たちは理解しているだろうか。

GraphQLの強みである「柔軟なデータ取得」は、セキュリティの観点では「攻撃者の自由度」と同義だ。特に、特定のフィールドに対する認可を怠ると、本来見せてはいけない個人情報や管理用フラグが、たった一行のクエリ追加で簡単に引き抜かれる。さらに、それがXSSの「格納場所」として悪用されたら? 想像するだけで冷や汗が出るはずだ。

今日は、GraphQLにおけるフィールドレベル認可の急所と、それを実装レベルでどう「完封」するか、泥臭い実践論を話そう。

—

1. なぜ「クエリ単位の認可」では不十分なのか

REST APIなら、/api/user/profile というエンドポイント一つを叩けば済む。しかし、GraphQLは違う。攻撃者は、認可チェックが「リゾルバの入り口」にしかないことを知っている。

攻撃シナリオ:XSSペイロードの注入と取得

例えば、掲示板アプリで comment フィールドに悪意あるスクリプトを保存(格納型XSS)し、それを管理者権限が必要な adminNotes フィールドと一緒に取得しようとする攻撃を考えてみよう。

query {
comment(id: 123) {
body # ここに が入っている
adminNotes # 本来は管理者しか見えないはずの機密情報
}
}

もし、adminNotes フィールドにアクセス権限チェック(認可)が入っていなければ、一般ユーザーが管理者情報を覗き見できてしまう。さらに、そのレスポンスがフロントエンドで適切にエスケープされずに表示されれば、クロスサイトスクリプティングが成立する。

—

2. フィールドレベル認可の決定版:GraphQLディレクティブ

最も堅牢で、かつコードが散らからない手法は「ディレクティブ」による認可だ。ロジックをリゾルバの中にベタ書きするな。それは技術的負債の始まりだ。

JavaScript (Apollo Server) での実装例

まずは、認可ロジックをディレクティブとして切り出す。

// schema.graphql
directive @auth(role: String) on FIELD_DEFINITION

type User {
id: ID!
email: String @auth(role: “ADMIN”) # ADMIN以外がアクセスしたらエラーを返す
publicName: String
}

// schema.js (実装の断片)
const { mapSchema, getDirective, MapperKind } = require(‘@graphql-tools/utils’);

function authDirectiveTransformer(schema) {
return mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
const authDirective = getDirective(schema, fieldConfig, ‘auth’)?.[0];
if (authDirective) {
const { resolve = defaultFieldResolver } = fieldConfig;
fieldConfig.resolve = async function (source, args, context, info) {
// コンテキストからユーザー権限を確認
if (!context.user || context.user.role !== authDirective.role) {
throw new Error(‘権限不足です。このフィールドにはアクセスできません。’);
}
return resolve(source, args, context, info);
};
return fieldConfig;
}
}
});
}

このアプローチの利点は、「スキーマ定義を見ただけで権限設計がわかる」ことにある。セキュリティ対策において、「どこに何があるか見えない」のは悪手だ。

—

3. 防御の要:XSSに対する「多層防御」

GraphQLでフィールド権限を固めたとしても、Webアプリの基本を忘れてはいけない。XSS対策の鉄則をGraphQLと組み合わせる。

1. レスポンスのサニタイズ: サーバー側で取得したデータをクライアントに返す際、必要に応じて DOMPurify 等を通す。
2. Content Security Policy (CSP): インラインスクリプトを禁止し、悪意あるコードが実行されても外部へのデータ送信をブロックする。

Nginx での CSP 設定例

万が一のXSS発生時に、被害を最小限に抑えるためのヘッダー設定だ。

信頼できるソース以外からのスクリプト実行を禁止
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’; object-src ‘none’;” always;
悪意あるレスポンスをブラウザ側で実行させない
add_header X-Content-Type-Options “nosniff” always;
add_header X-XSS-Protection “1; mode=block” always;

—

4. 現場のシニアとしてのアドバイス

「とりあえず動く」コードを書くのはジュニアでもできる。だが、「攻撃者がどう裏をかこうとするか」を予測して、あらかじめそのルートを遮断するのが、我々エンジニアの仕事だ。

  • リゾルバを巨大にするな: ロジックが複雑になると、認可チェックの抜け漏れが必ず発生する。前述のディレクティブのように、認可を「宣言的」に実装しろ。
  • 認可漏れを自動テストしろ: 権限のないユーザーでクエリを投げ、エラーが正しく返るかをCI/CDのテストケースに組み込むこと。手動テストに頼るな。
  • ログを信じるな: GraphQLはエラーが返っても200 OKを返すことがある。WAFのログなどで、異常なクエリパターン(大量のフィールド取得や、認可エラーの多発)を常時監視せよ。

GraphQLは強力だ。だが、その力は「正しく制御してこそ」輝く。今日紹介したディレクティブの実装をベースに、自分のプロジェクトのスキーマを今すぐ見直してほしい。セキュリティは「守り」ではない。システムを壊さないための、最強の「攻めの設計」なんだ。

コメント

タイトルとURLをコピーしました