スキーマチュートリアル
スキーマチュートリアルレッスン21: サービスへの接続時に認証情報を漏洩させない

レッスン21: サービスへの接続時に認証情報を漏洩させない

このGraphQLクエリは、環境変数から認証情報を取得し、レスポンスやログに出力されないようにすることで、セキュリティリスクを回避します:

query {
  githubAccessToken: _env(name: "GITHUB_ACCESS_TOKEN")
    @remove
 
  _sendJSONObjectItemHTTPRequest(input:{
    url: "https://api.github.com/repos/GatoGraphQL/GatoGraphQL",
    method: PATCH,
    options: {
      auth: {
        password: $__githubAccessToken
      },
      body: "{\"has_wiki\":false}"
    }
  })
}

以下は、このクエリの仕組みについての説明です。

認証情報が漏洩する可能性のある状況

外部サービスへ接続する際、認証情報の提供が必要になることがよくあります。たとえば、GitHubのREST APIでは、データがプライベートであるか変更を行うエンドポイントにはアクセストークンが必要です:

query {
  _sendJSONObjectItemHTTPRequest(input:{
    url: "https://api.github.com/repos/GatoGraphQL/GatoGraphQL",
    method: PATCH,
    options: {
      auth: {
        password: "{ GITHUB_ACCESS_TOKEN }"
      },
      body: "{\"has_wiki\":false}"
    }
  })
}

認証情報を公開しないよう注意する必要があります:

  • GraphQLクエリ内: 認証情報をソースコードに直接埋め込んではなりません。平文で記録されるためセキュリティ上の危険が生じます
  • GraphQLレスポンス内: サービスへ接続するフィールドがエラーを発生させた場合、GraphQLレスポンスの errors エントリにエラーメッセージが追加されます。このメッセージには失敗したフィールド名と引数が含まれる場合があり、認証情報が出力されてしまう可能性があります
  • サーバーログ内: 認証情報が変数経由でアクセスされ、その変数がURLパラメータとして提供される場合、Webサーバーのログに記録される可能性があります

認証情報の漏洩を回避するGraphQLクエリ

このGraphQLクエリは、認証情報を漏洩させることなくGitHubのAPIへ渡します:

query {
  githubAccessToken: _env(name: "GITHUB_ACCESS_TOKEN")
    @remove
 
  _sendJSONObjectItemHTTPRequest(input:{
    url: "https://api.github.com/repos/GatoGraphQL/GatoGraphQL",
    method: PATCH,
    options: {
      auth: {
        password: $__githubAccessToken
      },
      body: "{\"has_wiki\":false}"
    }
  })
}

その理由は以下のとおりです:

  • 認証情報は環境変数 GITHUB_ACCESS_TOKEN から取得されるため、ソースコードに埋め込む必要がありません
  • フィールド githubAccessToken@remove によって削除されるため、レスポンスに出力されません
  • _sendJSONObjectItemHTTPRequest(auth:) の入力は動的変数 $__githubAccessToken を参照しているため、フィールドがエラーを生成した場合、エラーメッセージに出力されるのはリテラル文字列 "$__githubAccessToken" であり、その値ではありません

最後の点を示すために、存在しないリポジトリ "leoloso/NonExisting" のURLをGitHubのAPIへ渡すとエラーが発生し、次のようなレスポンスが返されます(エラーメッセージ内の auth: {password: $__githubAccessToken} に注目してください):

{
  "errors": [
    {
      "message": "Client error: `PATCH https://api.github.com/repos/leoloso/NonExisting` resulted in a `404 Not Found` response:\n{\"message\":\"Not Found\",\"documentation_url\":\"https://docs.github.com/rest/repos/repos#update-a-repository\"}\n",
      "locations": [
        {
          "line": 21,
          "column": 3
        }
      ],
      "extensions": {
        "path": [
          "_sendJSONObjectItemHTTPRequest(input: {url: \"https://api.github.com/repos/leoloso/NonExisting\", method: PATCH, options: {auth: {password: $__githubAccessToken}, body: \"{\"has_wiki\":false}\"}})",
          "query { ... }"
        ],
        "type": "QueryRoot",
        "field": "_sendJSONObjectItemHTTPRequest(input: {url: \"https://api.github.com/repos/leoloso/NonExisting\", method: PATCH, options: {auth: {password: $__githubAccessToken}, body: \"{\"has_wiki\":false}\"}})",
        "id": "root",
        "code": "PoP/ComponentModel@e1"
      }
    }
  ],
  "data": {
    "_sendJSONObjectItemHTTPRequest": null
  }
}