Tutorial del esquema
Tutorial del esquemaLección 21: No filtrar credenciales al conectarse a servicios

Lección 21: No filtrar credenciales al conectarse a servicios

Esta consulta GraphQL recupera credenciales desde un valor de entorno, y evita que se impriman en la respuesta o logs, evitando así riesgos de seguridad:

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}"
    }
  })
}

A continuación se explica cómo funciona esta consulta.

Cómo se podrían filtrar las credenciales

A menudo necesitamos proporcionar credenciales al conectarnos a servicios externos. Por ejemplo, la API REST de GitHub requiere un token de acceso para endpoints donde los datos son privados o se mutan:

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

Tenemos que tener cuidado y evitar exponer nuestras credenciales:

  • En la consulta GraphQL: Las credenciales nunca deben estar embebidas en el código fuente, ya que estarán en texto plano, creando un riesgo de seguridad
  • En la respuesta GraphQL: Si el campo que se conecta al servicio produce un error, se añadirá un mensaje de error en la respuesta GraphQL bajo la entrada errors; este mensaje podría imprimir el nombre del campo que falló junto con sus argumentos, imprimiendo así las credenciales
  • En los logs del servidor: Si las credenciales se acceden mediante una variable, y esta variable se proporciona como parámetro URL, entonces podría ser registrada en los logs del servidor web

Consulta GraphQL que evita filtrar credenciales

Esta consulta GraphQL pasa las credenciales a la API de GitHub mientras evita filtrar las credenciales:

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}"
    }
  })
}

Esto es porque:

  • Las credenciales se recuperan desde una variable de entorno GITHUB_ACCESS_TOKEN, por lo que no necesitan estar embebidas en el código fuente
  • El campo githubAccessToken se elimina con @remove, por lo que no se imprime en la respuesta
  • La entrada _sendJSONObjectItemHTTPRequest(auth:) referencia la variable dinámica $__githubAccessToken, por lo que si el campo produce un error, será el string literal "$__githubAccessToken" el que se imprima en el mensaje de error (no su valor)

Para demostrar el último elemento, proporcionando la URL de un repositorio inexistente "leoloso/NonExisting" a la API de GitHub se produce un error, y obtenemos esta respuesta (nótese auth: {password: $__githubAccessToken} en el mensaje de error):

{
  "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
  }
}