Hacer frente a la autorización del usuario en GraphQL con AWS AppSync

Cómo implementar la autorización del usuario y el control de acceso detallado en una aplicación GraphQL usando AWS AppSync con Amazon Cognito y AWS Amplify.

Si esta es la primera vez que utiliza AWS AppSync, probablemente le recomiende que consulte este tutorial antes de seguir aquí.

Si ya está familiarizado con AWS AppSync y desea profundizar en ejemplos de autorización de usuarios más complejos, consulte esta publicación reciente de Richard Threlkeld .

En esta publicación, veremos cómo permitir que los usuarios autorizados accedan a los datos en una API de GraphQL. También mostraremos cómo identificar correctamente al usuario autenticado actualmente de forma segura en AWS AppSync, almacenando su nombre de usuario en la base de datos como su identificador único cuando crean recursos.

Aunque vamos a hacer esto en el contexto de una aplicación de React, las técnicas que estamos revisando funcionarán con la mayoría de los frameworks de JavaScript, incluyendo Vue, React, Reacces Native, Ionic y Angular.

El flujo con el que trabajaremos se ve así:

  1. Como usuario, inicia sesión en la aplicación y recibimos un token de identidad.
  2. Invocamos una consulta o mutación GraphQL de la aplicación cliente, pasando el token de identidad del usuario junto con la solicitud en un encabezado de autorización (la identidad pasó automáticamente el cliente de AWS AppSync).
  3. En nuestro sistema de resolución de problemas, buscamos ciertos datos, en nuestro caso el nombre de usuario del usuario, para realizar operaciones de forma condicional, realizar consultas basadas en el usuario actual o crear mutaciones utilizando el nombre de usuario del usuario registrado actualmente.
  4. La operación se ejecuta o rechaza como no autorizada según la lógica declarada en nuestro resolvedor.

El flujo de datos para una mutación podría verse más o menos así:

  1. El usuario ejecuta una operación GraphQL enviando sobre sus datos como una mutación.
  2. El solucionador actualiza los datos para agregar la información del usuario que se decodifica desde el JWT. El JWT se envía en el encabezado de autorización y está disponible en el resolver.
  3. Los datos se almacenan en la base de datos junto con la información del usuario.

En este ejemplo, ahora podemos realizar consultas basadas en el índice del autor.

Empezando

Las herramientas que utilizaremos para lograr esto son AWS Amplify como nuestro SDK de cliente para Autenticación, así como nuestro cliente GraphQL y AWS Mobile CLI para crear nuevos servicios de AWS Mobile Hub.

Si aún no está familiarizado con el uso de AWS Amplify con Cognito para autenticar a un usuario y desea obtener más información, revise Autenticación de réplica en profundidad o Autenticación de reactivación nativa en profundidad .

También creé un video tutorial que repasa todo lo que discutiremos en esta publicación de blog, para verlo, haga clic aquí .

Para comenzar, clone la plantilla que vamos a utilizar en este ejemplo:

 git clone https://github.com/dabit3/appsync-react-native-with-user-authorization 

Luego, cd en el directorio e instale las dependencias usando hilo o npm:

 cd appsync-react-native-with-user-authorization 
 hilo || npm i 

Ahora que las dependencias están instaladas, utilizaremos AWS Mobile CLI para inicializar un nuevo proyecto.

Primero, instale la CLI awsmobile si aún no la tiene instalada:

 npm i -g awsmobile-cli 

A continuación, configure el cli con sus credenciales correctas:

Si esta es la primera vez que utiliza AWS, consulte este video para ver cómo obtener estas credenciales y configurar la CLI.

 awsmobile configure 

Ahora podemos crear un nuevo proyecto:

 awsmobile init 

Se le solicitarán algunas opciones de configuración, puede aceptarlas por defecto o elegir un nombre de proyecto personalizado cuando se le presente la opción.

A continuación, agregaremos capacidades de inicio de sesión de usuario a la aplicación con Amazon Cognito:

 awsmobile user-signin enable 

A continuación, inserte la configuración actualizada en la consola de AWS

 empuje awsmobile 

Ahora, debería poder visitar la consola y ver el nuevo proyecto. Vaya a https://console.aws.amazon.com/mobilehub/home y haga clic en el nombre de su proyecto para ver su configuración actual.

Creando la API AppSync de AWS

Ahora que nuestro proyecto de AWS Mobile se creó y está listo para comenzar, creemos nuestra API de AWS AppSync.

Primero, vaya a la consola de AWS AppSync visitando https://console.aws.amazon.com/appsync/home y haciendo clic en Crear API .

Creando el esquema

Seleccione Esquema personalizado y asigne un nombre a su API.

Ahora que se ha creado la API, haga clic en Configuración y actualice el tipo de Autorización para que sea el grupo de usuarios de Amazon Cognito . En la configuración del grupo de usuarios, elija el grupo de usuarios que se creó cuando creamos nuestro proyecto AWS Mobile utilizando la CLI junto con su región, y establezca la acción predeterminada en Permitir .

A continuación, cree el siguiente esquema y haga clic en Guardar :

 tipo Ciudad { 
¡yo si!
nombre: ¡Cadena!
país: ¡Cuerda!
autor: Cadena
}
 tipo Query { 
fetchCity (id: ID): Ciudad
}

Tenga en cuenta que autor es el único campo no requerido.

Recursos de aprovisionamiento

A continuación, haz clic en el botón Crear recursos.

En esta pantalla, elija Ciudad como tipo y cree un índice adicional con un nombre de author-index de author-index y una clave principal de author . Luego desplácese hacia abajo y haga clic en Crear .

Actualización de los resolutores

A continuación, actualizaremos un par de resolutores. En primer lugar, queremos asegurarnos de que cuando creamos una nueva ciudad, el nombre de usuario del usuario se almacene en el campo de autor. Esta información de nombre de usuario está disponible como parte del token de identidad del usuario pasado junto con la solicitud en un encabezado de autorización, y podemos acceder a esto en nuestro resolvedor como la identidad en el campo de context.identity disponible en el resolver.

Identificando el usuario actualmente conectado en una mutación

En el campo de resolución en Tipos de datos de mutación en el panel, haga clic en el resolver para createCity :

Actualice la plantilla de asignación de solicitud de createCity a lo siguiente:

 #set ($ attribs = $ util.dynamodb.toMapValues ??($ ctx.args.input)) 
#set ($ attribs.author = $ util.dynamodb.toDynamoDB ($ ctx.identity.nombredeusuario))
{
"versión": "2017-02-28",
"operación": "PutItem",
"llave": {
"id": $ util.dynamodb.toDynamoDBJson ($ ctx.args.input.id),
},
"attributeValues": $ util.toJson ($ attribs),
"condición": {
"expresión": "attribute_not_exists (#id)",
"expressionNames": {
"#yo si",
},
},
}

Hay un par de cosas a tener en cuenta:

  1. En la primera línea de código estamos creando un nuevo mapa / objeto llamado $attribs y agregando los valores existentes que vienen como $ctx.args.input
  2. En la segunda línea de código estamos agregando otro campo al objeto llamado author con el valor de $ctx.identity.username . $ctx.identity es la información de identidad que llega a través del token de identidad del usuario.
  3. En el campo attributeValues , estamos pasando el nuevo objeto $attribs que acabamos de crear.

Ahora, cuando creamos una nueva ciudad, la identidad del usuario se almacenará automáticamente como otro campo en la tabla DynamoDB.

Permitir acceso de lectura solo al autor del elemento

Ahora que tenemos una forma de identificar al usuario en una mutación, lleguemos a donde, cuando un usuario solicite los datos, los únicos campos a los que puede acceder son los propios.

Para agregar esta funcionalidad usando nuestra configuración existente, solo tenemos que hacer una cosa: actualizar la resolución de listCities para consultar solo los datos creados por el usuario actualmente conectado.

DynamoDB le permite realizar operaciones de Query directamente en un índice. Utilizaremos esto consultando los datos de la tabla usando el author-index y nuevamente utilizando $context.identity.username de usuario para identificar al usuario.

Actualice la request mapping template listCities a lo siguiente:

 { 
"versión": "2017-02-28",
"operación": "Consulta",
"índice": "autor-índice",
"consulta" : {
"expresión": "autor =: autor",
"expressionValues": {
": autor": {
"S": "$ {ctx.identity.nombredeusuario}"
}
}
},
"nextToken": $ util.toJson ($ util.defaultIfNullOrEmpty ($ ctx.args.after, null)),
}

Un par de cosas cambiaron aquí:

  1. Actualizamos la operación para que sea una Query frente a una Scan . La diferencia es que una operación de scan escanea toda la tabla mientras que una operación de query solo busca valores de atributos de clave primaria y admite un subconjunto de operadores de comparación en valores de atributos clave (en nuestro caso, autor) para refinar el proceso de búsqueda.
  2. Agregamos un campo de index especifica el índice en el que nos gustaría consultar
  3. Agregamos otro campo nuevo, query , que define el valor que nos gustaría usar como valor de consulta (en nuestro caso, autor).

Ahora, la API está completa y podemos comenzar a probarla.

Probando la aplicación

Debería poder ejecutar la aplicación ejecutando react-native run-ios o react-native run-android .

Desde la pantalla de inicio, elija "Registrarse" y cree un nuevo usuario. Confirme al nuevo usuario con autenticación de 2 factores (asegúrese de agregar +1 o su código de país cuando ingrese su número de teléfono).

Una vez que se haya registrado, inicie sesión, haga clic en Agregar ciudad y cree una nueva ciudad:

Una vez que haya creado una ciudad, debería poder hacer clic en la pestaña Ciudades para ver esta nueva ciudad.

Ahora, volvamos al panel de AWS AppSync. Haga clic en Fuentes de datos y el nombre de la tabla. Esto te llevará a DynamoDB.

En la pestaña de elementos, ahora debería poder ver los campos junto con el nuevo campo Autor.

Si agrega manualmente una nueva entrada a la base de datos con otro nombre de autor, o actualiza un campo existente cambiando el nombre del autor a uno que no es el suyo y actualiza su aplicación, estas ciudades con los campos actualizados no deberían aparecer en su aplicación ¡ya que el resolver devolverá solo los campos que ha escrito!

Conclusión

Al construir una aplicación del mundo real hay muchas cosas importantes y complejas que deben tenerse en cuenta, una de las más importantes es una historia de autorización de usuario escalable y fácil de implementar en el mundo real.

Al usar GraphQL, también debe tener en cuenta las mejores prácticas en cuanto a la escalabilidad y la seguridad.

GraphQL le da el poder de aplicar diferentes controles de autorización para casos de uso como:

  • Una API pública
  • Acceso privado y público a secciones de una API
  • Registros privados y públicos, verificados en tiempo de ejecución en los campos
  • Uno o más usuarios pueden escribir / leer un registro (s)
  • Uno o más grupos pueden escribir / leer en un registro (s)
  • Todos pueden leer, pero solo los creadores de registros pueden editar o eliminar

Una de las cosas más convincentes acerca de AWS AppSync es su poderosa función de autorización de usuario incorporada que permite que todos estos casos de uso de autorización de usuario de GraphQL se manejen de manera inmediata.

Mi nombre es Nader Dabit . Soy un Defensor de Desarrolladores en AWS Mobile que trabaja con proyectos como AWS AppSync y AWS Amplify , y el fundador de React Native Training .

Si disfrutaste este artículo, aplaude n veces y ¡compártelo! Gracias por tu tiempo.

Imágenes cortesía de Amazon Web Services, Inc

Texto original en inglés.