Node.jsで安全なGraphQL APIを構築する
Lawrence Eagles
2022年3月29日
0 分で読めますGraphQLには、検証や型チェックなどのセキュリティ機能が標準で備わっています。しかし、APIに関するセキュリティ上の懸念にすべて対処できるわけではありません。この記事では、FastifyとGraphQLを使ってシンプルなNode.jsアプリケーションを構築し、GraphQL APIを安全にする方法を学びます。
公式ドキュメントによると、GraphQLはAPI向けのグラフクエリ言語であり、データを使ってクエリを実行するためのランタイムです。GraphQLを使うと、API内のデータを明確に記述でき、クライアントが必要なデータを完全に指定できる、柔軟で高速なAPIを作成できます。RESTと同様に、GraphQLはHTTP上で動作するため、データベースに依存せず、あらゆるバックエンド言語やクライアントで利用できます。
Fastifyはプラグインベースの効率性と高いパフォーマンスを備えたNode.jsフレームワークで、高速なHTTPサーバーの構築に適しています。HapiとExpressに着想を得たFastifyは、オーバーヘッドを抑え、開発者にとって使いやすく、より高性能な選択肢を提供します。
FastifyはMercuriusプラグインを使ってGraphQLをサポートします。Mercuriusプラグインは、Fastify向けに設定可能なGraphQLアダプターです。以降のセクションで詳しく説明します。
まず、前提条件を確認しましょう。
前提条件
この記事を進めるには、以下が必要です。
Node.js バージョン12以降
JavaScriptの基本的な知識
GraphQLの基本的な知識
はじめに
まず、基本的なNode.jsサーバーを作成します。
プロジェクトフォルダーを作成してスタータープロジェクトを用意し、そのフォルダーからコマンドラインインターフェース(CLI)で以下のコードを実行します。これにより、アプリケーションの初期設定を行い、必要な依存関係をインストールします。
このプロジェクトでは、特定のバージョンのMercuriusを使用します。インストールするには、次のコマンドを実行してください。
次に、"type": "module"をpackage.jsonファイルに追加し、commonJSではなく標準のJavaScriptモジュールシステムを使えるようにES6モジュールを有効にします。
続いて、package.jsonファイルを開き、scriptsセクションを次のように編集して、Node.jsサーバーを起動するコマンドをNPMスクリプトに追加します。
ESモジュールとNodeのcommonJSモジュール間の相互運用性を有効にするには、--es-module-specifier-resolution=nodeの記述が必要です。
次に、ルートディレクトリにsrcディレクトリを作成します。srcディレクトリにgraphqlフォルダーを作り、その中にschema.jsとresolvers.jsファイルを作成します。schema.jsファイルに次のコードを追加してください。
次に、resolvers.jsファイルに以下のコードを追加します。
次のセクションでschema.jsを更新し、resolvers.jsを追加します。ただし、サーバーを正しく動作させるために必要なので、まずは定型コードを使ってこれらのファイルを作成しておきます。
srcディレクトリにindex.jsファイルを作成し、次のコードを記述します。
上記のコードでは、基本的なFastifyサーバーを作成し、schema、resolvers、graphiql、queryDepthのオプションを指定してMercuriusプラグインを登録しています。
これで、npm run devを実行してサーバーを起動できます。出力は次のとおりです。
サーバーが動作していることを確認できました。次のセクションでは、GraphQLを使ってブログAPIを構築します。
FastifyとGraphQLで安全なブログAPIを構築する
APIを安全にする方法はいくつかあります。たとえば、次のような方法です。
認証と認可:認証は、ユーザーが本人であることを確認する仕組みです。認可は、ユーザーに付与された権限を扱います。認証によってユーザーがログインできるかどうかを判断し、その後もユーザーを識別できるようにします。認可では、識別済みのユーザーに割り当てる権限を定め、作成、読み取り、更新、削除などの操作を実行できるかどうかを制御します。
エラー情報のマスキング:サーバーエラーの詳細を隠し、サーバーの脆弱性を明らかにする可能性のある情報を、誤ってクライアントに提供しないようにします。
クエリの深さ制限:GraphQLクエリの最大深度を指定します。深くネストされたクエリは、多くのリソースを消費し、計算コストも高いため危険です。その結果、APIがクラッシュする可能性があります。
入力値のサニタイズと検証:標準的なWebセキュリティ技術を使い、ユーザーが悪意のあるデータを送信できないようにします。ここでは、アプリケーションに組み込まれているGraphQLの検証機能を活用します。
この記事では、MercuriusとMercurius Authプラグインを使い、上記の方法でAPIを構築して安全にします。
今回使用するMercurius Authプラグインには、主に2つの機能があります。1つ目は、スキーマ内のフィールドにカスタム認証ディレクティブを定義できることです。認証ディレクティブは、スキーマ内の保護対象フィールドを識別するための文字列です。
さらに、GraphQLリクエストを処理する際に、これらの保護対象フィールドにカスタム認証ポリシーを適用できます。
まず、モックデータを作成します。srcディレクトリにdataフォルダーを作成し、その中に以下のコードを記述したindex.jsファイルを用意します。
次に、schema.jsファイルの定型コードを以下のコードに置き換えて、schemaを設定します。
上記のコードではGraphQLスキーマを作成し、userフィールドとusersフィールドに認証ディレクティブを定義しました。続いて、これらの保護対象フィールドにカスタムポリシーを適用します。
次に、resolvers.jsファイルの定型コードを以下のコードに置き換えて、リゾルバーを追加します。
上記のコードには、user、users、loginの各クエリを処理するリゾルバーが含まれています。
最後に、Mercurius Authプラグインを登録して、カスタム認証ポリシーを追加します。srcディレクトリにあるindex.jsファイルで、19行目にあるregister auth policyコメントの下に以下のコードを追加してください。
上記のカスタムポリシーでは、authContextメソッドがヘッダーからユーザートークンを取得し、applyPolicyメソッドが認証と認可のカスタムポリシーを定義します。
また、ユーザーの認証または認可に失敗した場合は、「エラーが発生しました。もう一度お試しください!」のような汎用メッセージを含むエラーをスローします。このメッセージは、サーバーの脆弱性を明らかにするおそれのある詳細なサーバーエラーメッセージの代わりにユーザーに表示されます。
これで実装は完了です。次のセクションでAPIをテストします。
APIをテストする
まず、ルートディレクトリでnpm run devを実行してサーバーを起動します。次に、http://localhost:4500/playgroundからGraphQL Playgroundにアクセスします。
usersやuserなどの保護対象APIにクエリを実行すると、以下のようなエラーが表示されます。

クエリを成功させるには、認証が必要です。トークンを取得するためにログインしましょう。
ログインするには、Playgroundで新しいタブを開き、次のクエリを実行します。
クエリが成功すると、以下の画像のようにトークンが生成され、返されます。

ログインに使うユーザーデータは、dataディレクトリ内のindex.jsファイルにあらかじめ記載されています。
そのファイルにないユーザーデータ(username: “John1Doe”など)でログインしようとすると、以下のようなエラーになります。

次に、以下のようにx-userとしてトークンをヘッダーに渡すと、保護対象APIに正常にクエリを実行できます。ログインクエリで取得したトークンをコピーし、x-userの値として使ってください。
usersクエリ
usersクエリの例を以下に示します。
以下のように、トークンをx-user HTTPヘッダーパラメーターの値として追加します。
実行すると、次の結果が得られます。

userクエリ
userクエリの例を以下に示します。
以下のように、トークンをx-user HTTPヘッダーパラメーターの値として追加します。
実行すると、次の結果が得られます。

まとめ
この記事では、FastifyとGraphQLを使ってシンプルなNode.jsアプリケーションを構築し、GraphQL APIを簡単に安全にできることを紹介しました。
説明したように、GraphQLには検証や型チェックなどのセキュリティ機能が組み込まれています。しかし、ユーザーが自由にデータを要求できる柔軟性と強力さがあるからこそ、セキュリティを常に最優先事項として考える必要があります。
この記事では、GraphQL APIを安全にする方法として、認証と認可、クエリの深さ制限、エラー情報のマスキング、入力値のサニタイズと検証についても説明しました。
これらのセキュリティ対策は非常に有効ですが、クエリのタイムアウトやレート制限などの方法を実装すれば、さらにセキュリティを強化できます。レート制限では、各期間内にクライアントがAPIにクエリを実行できる頻度を指定します。
CTFを始めよう
オンデマンドのバーチャル入門ワークショップで、CTFチャレンジの解き方を学びましょう。
無料のオンラインJavaScriptコードチェッカーをお試しください。Snyk Codeエンジンが、コードのセキュリティと品質に関する問題をどのように分析するかをご確認いただけます。
