Blog de Amazon Web Services (AWS)

Autenticación de sus aplicaciones a través de reconocimiento facial utilizando el Amazon Cognito y el Amazon Rekognition

Con el paso del tiempo, acaba siendo notorio el aumento del uso de diferentes aplicaciones, redes sociales, plataformas financieras, correos electrónicos, plataformas de almacenamiento en nube, etc. Y con ello, gestionar diferentes contraseñas y las credenciales pueden convertirse en un desafío un tanto doloroso.

En muchos casos, ya no se usa el compartir una sola contraseña a través de todas estas aplicaciones y plataforma. Se requieren estándares de seguridad diferentes, composiciones sólo con caracteres numéricos, políticas de renovación de las contraseñas, por la simple cuestión de seguridad.

Pero, ¿Y si pudiéramos ampliar las formas en la que los usuarios se autentican en sus aplicaciones de una manera más simple, conveniente, y por encima de todo, seguro?

En este artículo muestra cómo utilizar los Grupos de usuarios de Amazon Cognito para personalizar sus flujos de autenticación y permitir el acceso a sus aplicaciones con la ayuda del Amazon Rekognition para el reconocimiento facial a través de una aplicación sencilla.

Visión general de la solución

Al realizar el registro de un nuevo usuario en una aplicación Mobile o Web, además de definir un e-mail, solicitamos que el usuario cargue un documento con foto. Utilizando el Amplify Framework, podemos fácilmente integrar nuestra aplicación Front-End con Amazon S3 y almacenar esta imagen en un bucket seguro y encriptado. Una función Lambda se disparará para cada nueva imagen dentro de este bucket, para que pueda indizar la imagen dentro del Amazon Rekognition a grabar en una tabla de DynamoDB sus metadatos para las consultas posteriores.

Para la autenticación, esta solución utiliza Amazon Cognito user pools con funciones Lambda, para personalizar los flujos de trabajo de autenticación junto con la API de CompareFaces de Amazon Rekognition. Esto, para identificar el nivel de asertividad en la imagen pasada en el momento del registro del usuario.

El flujo anterior describe los pasos recorridos por la solución:

  1. El usuario realiza el Sign Up registrando sus datos en el User Pool.
  2. El usuario adjunta, en el momento del Sign Up, una foto de un documento con nombre y foto, por ejemplo, un pasaporte, que se carga a un S3 Bucket.
  3. Una función Lambda se dispara con los datos de la imagen cargada en el búfer.
  4. La función indiza la imagen dentro de la colección específica de documentos de usuario creada en Amazon Rekognition.
  5. La misma función, después de la indexación, persiste en una tabla de DynamoDB los metadatos de la imagen indexada, junto con el e-mail registrado en el Cognito user pool para consultas posteriores.
  6. El usuario entra un correo electrónico en la página de Sign In, que envía la solicitud a Cognito user pools.
  7. El grupo de usuarios invoca la función de «Define Auth Challenge», que determina qué desafío personalizado debe ser creado en el momento.
  8. El grupo de usuarios invoca la función de «Create Auth Challenge», que consulta en DynamoDB por los datos del usuario o dueño del e-mail informado, para recuperar los datos de la imagen indexada en el Rekognition.
  9. El grupo de usuarios invoca la función de «Verify Auth Challenge», que comprueba si el reto anterior fue hecho con éxito, y si ha encontrado la imagen, compara con la foto tomada en el momento del Sign In para validar la asertividad en la comparación entre las dos imágenes.
  10. El grupo de usuarios invoca de nuevo la función «Define Auth Challenge», que comprueba que el desafío se ha respondido con éxito y que no existen nuevos retos a ser creados. Incluye un atributo de «issueTokens: true» en su sistema carga de respuesta para la agrupación de usuarios. El grupo de usuarios puede considerarse autenticado ahora, y se envía al usuario un JSON Web de Token (JWT) (respuesta a paso 6).

Aplicación Serverless

La implementación siguiente está disponible como una aplicación serverless. Usted puede hacer su implementación a desde el AWS Serverless Application Repository.

Las principales partes de esta aplicación son:

  • Obligatoriedad que los usuarios inscriben un e-mail válido.
  • El cliente de aplicaciones de Cognito User Pool está configurado como «Sólo permitir la autenticación personal», ya que un usuario se registra. Amazon Cognito requiere una contraseña para este usuario. Vamos a crear una contraseña aleatoria para este usuario, ya que no queremos que haga Sign In con esta contraseña posteriormente.
  • Dos S3 Buckets: uno para almacenar las imágenes de documentos cargados en el momento del Sign Up, y uno para almacenar las imágenes pasadas en el momento del Sign In para comparaciones.
  • Las siguientes funciones de Lambda, que implementan la indexación de imágenes en Amazon Rekognition y personalizan los desafíos de autenticación en Amazon Cognito User Pools:
    • Create Rekognition Collection (Python 3.6) – La función Lambda se dispara sólo una vez al principio para crear una colección personalizada en Amazon Rekognition, que servirá como indizador para los documentos cargados en el momento del Sign Up de cada usuario.
    • Index Faces (Python 3.6) – Esta función Lambda se dispara para cada nueva carga de documento Bucket S3 seleccionado, y se responsabiliza por indexar este documento en la colección del Amazon Rekognition y persiste sus metadatos en el DynamoDB.
    • Define Auth Challenge (Node.js 8.10) – Esta función Lambda rastrea el flujo de autenticación personalizado, que se compara con el de una función «decidir» de una máquina de estado. Ella determina qué desafío se debe crear para el usuario y en qué orden. Al final, reporta de vuelta a la agrupación de usuarios si el usuario ha pasado o no en la autenticación. Esta función se invoca al inicio del flujo de autenticación, y también después al término de cada función «Verify Auth Challenge Response»
    • Create Auth Challenge (Node.js 8.10) – Esta función Lambda se invoca basada en la instrucción de función «Define Auth Challenge», para crear un desafío único para el usuario. Utilizaremos esta función para buscar en DynamoDB si existen registros para el usuario en cuestión y si sus metadatos son válidos.
    • Verify Auth Challenge Response (Node.js 8.10) – Esta función Lambda es invocada por el grupo de usuarios cuando el usuario el usuario proporciona una respuesta al desafío creado. Su trabajo es simplemente validar si su respuesta está correcta. En este caso, comparar las imágenes pasadas en los momentos de Sign Up y Sign In usando la API de CompareFaces de Amazon Rekognition y, si el nivel de asertividad es mayor que el 90%, considera como respuesta válida.

Creando una colección en el Amazon Rekognition

Esta función es responsable sólo de crear una colección en Amazon Rekognition para recibir imágenes de documentos con fotos de usuarios en el momento del Sign Up.

import boto3
import os

def handler(event, context):

    maxResults=1
    collectionId=os.environ['COLLECTION_NAME']
    
    client=boto3.client('rekognition')

    #Create a collection
    print('Creating collection:' + collectionId)
    response=client.create_collection(CollectionId=collectionId)
    print('Collection ARN: ' + response['CollectionArn'])
    print('Status code: ' + str(response['StatusCode']))
    print('Done...')
    return response

Indexando Imágenes en Amazon Rekognition

Esta función es responsable de recibir los datos e indexarla en la colección del Amazon Rekognition y persistir sus metadatos en la tabla de DynamoDB.

from __future__ import print_function

import boto3
from decimal import Decimal
import json
import urllib
import os

dynamodb = boto3.client('dynamodb')
s3 = boto3.client('s3')
rekognition = boto3.client('rekognition')


# --------------- Helper Functions ------------------

def index_faces(bucket, key):

    response = rekognition.index_faces(
        Image={"S3Object":
            {"Bucket": bucket,
            "Name": key}},
            CollectionId=os.environ['COLLECTION_NAME'])
    return response
    
def update_index(tableName,faceId, fullName):
    response = dynamodb.put_item(
        TableName=tableName,
        Item={
            'RekognitionId': {'S': faceId},
            'FullName': {'S': fullName}
            }
        ) 
    
# --------------- Main handler ------------------

def handler(event, context):

    # Get the object from the event
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(
        event['Records'][0]['s3']['object']['key'].encode('utf8'))

    try:

        # Calls Amazon Rekognition IndexFaces API to detect faces in S3 object 
        # to index faces into specified collection
        
        response = index_faces(bucket, key)
        
        # Commit faceId and full name object metadata to DynamoDB
        
        if response['ResponseMetadata']['HTTPStatusCode'] == 200:
            faceId = response['FaceRecords'][0]['Face']['FaceId']
            ret = s3.head_object(Bucket=bucket,Key=key)
            email = ret['Metadata']['email']
            update_index(os.environ['COLLECTION_NAME'],faceId, email) 
        return response
    except Exception as e:
        print("Error processing object {} from bucket {}. ".format(key, bucket))
        raise e

Función Define Auth Challenge

Esta función es responsable de administrar el flujo de autenticación de la agrupación de usuarios. En el array de sesión (event.request.session), está presente todo el estado del flujo de autenticación.

Si está vacía, el flujo de autenticación acaba de comenzar. Si tiene elementos, el flujo está en curso: Uno, el desafío fue presentado al usuario, el usuario presentó una respuesta, y se verificó si es correcta o incorrecta. En cualquier caso, esta función decide qué hacer a continuación.

exports.handler = async (event, context) => {

    console.log("Define Auth Challenge: " + JSON.stringify(event));

    if (event.request.session &&
        event.request.session.length >= 3 &&
        event.request.session.slice(-1)[0].challengeResult === false) {
        // The user provided a wrong answer 3 times; fail auth
        event.response.issueTokens = false;
        event.response.failAuthentication = true;
    } else if (event.request.session &&
        event.request.session.length &&
        event.request.session.slice(-1)[0].challengeResult === true) {
        // The user provided the right answer; succeed auth
        event.response.issueTokens = true;
        event.response.failAuthentication = false;
    } else {
        // The user did not provide a correct answer yet; present challenge
        event.response.issueTokens = false;
        event.response.failAuthentication = false;
        event.response.challengeName = 'CUSTOM_CHALLENGE';
    }

    return event;
}

Función Create Auth Challenge

Esta función busca en DynamoDB un registro que contenga el correo electrónico proporcionado por el usuario y recupere la ID de su objeto dentro de la Colección de reconocimiento de Amazon, y define como un desafío al usuario proporcionar una foto que haga referencia a la misma persona.

const aws = require('aws-sdk');
const dynamodb = new aws.DynamoDB.DocumentClient();

exports.handler = async (event, context) => {

    console.log("Create auth challenge: " + JSON.stringify(event));

    if (event.request.challengeName == 'CUSTOM_CHALLENGE') {
        event.response.publicChallengeParameters = {};

        let answer = '';
        // Querying for Rekognition ids for the e-mail provided
        const params = {
            TableName: process.env.COLLECTION_NAME,
            IndexName: "FullName-index",
            ProjectionExpression: "RekognitionId",
            KeyConditionExpression: "FullName = :userId",
            ExpressionAttributeValues: {
                ":userId": event.request.userAttributes.email
            }
        }
        
        try {
            const data = await dynamodb.query(params).promise();
            data.Items.forEach(function (item) {
                
                answer = item.RekognitionId;

                event.response.publicChallengeParameters.captchaUrl = answer;
                event.response.privateChallengeParameters = {};
                event.response.privateChallengeParameters.answer = answer;
                event.response.challengeMetadata = 'REKOGNITION_CHALLENGE';
                
                console.log("Create Challenge Output: " + JSON.stringify(event));
                return event;
            });
        } catch (err) {
            console.error("Unable to query. Error:", JSON.stringify(err, null, 2));
            throw err;
        }
    }
    return event;
}

Función Verify Auth Challenge Response

Esta función es responsable de verificar en Amazon Rekognition si se puede encontrar una imagen con asertividad igual o superior al 90% con la imagen enviada en el momento del Sign In, y si la imagen hace referencia de hecho a la misma persona que el usuario alega ser a través del correo electrónico pasado.

var aws = require('aws-sdk');
var rekognition = new aws.Rekognition();

exports.handler = async (event, context) => {

    console.log("Verify Auth Challenge: " + JSON.stringify(event));
    let userPhoto = '';
    event.response.answerCorrect = false;

    // Searching existing faces indexed on Rekognition using the provided photo on s3

    const objectName = event.request.challengeAnswer;
    const params = {
        "CollectionId": process.env.COLLECTION_NAME,
        "Image": {
            "S3Object": {
                "Bucket": process.env.BUCKET_SIGN_UP,
                "Name": objectName
            }
        },
        "MaxFaces": 1,
        "FaceMatchThreshold": 90
    };
    try {
        const data = await rekognition.searchFacesByImage(params).promise();

        // Evaluates if Rekognition was able to find a match with the required 
        // confidence threshold

        if (data.FaceMatches[0]) {
            console.log('Face Id: ' + data.FaceMatches[0].Face.FaceId);
            console.log('Similarity: ' + data.FaceMatches[0].Similarity);
            userPhoto = data.FaceMatches[0].Face.FaceId;
            if (userPhoto) {
                if (event.request.privateChallengeParameters.answer == userPhoto) {
                    event.response.answerCorrect = true;
                }
            }
        }
    } catch (err) {
        console.error("Unable to query. Error:", JSON.stringify(err, null, 2));
        throw err;
    }
    return event;
}

Aplicación Front-End

Para coordinar, es necesario crear una página de Sign In personalizada. Puede utilizar el AWS Amplify Framework para integrar su página de Sign In con Amazon Cognito y subir fotos con Amazon S3.

Amplify Framework le permite implementar sus páginas utilizando su marco favorito (React, Angular, Vue, HTML / JavaScript, etc.). Los ejemplos siguientes se pueden personalizar para satisfacer sus necesidades específicas.

El siguiente fragmento muestra cómo importar e iniciar el AWS Amplify Framework utilizando React:

import Amplify from 'aws-amplify';

Amplify.configure({
  Auth: {
    region: 'your region',
    userPoolId: 'your userPoolId',
    userPoolWebClientId: 'your clientId',
  },
  Storage: { 
    region: 'your region', 
    bucket: 'your sign up bucket'
  }
});

Realizando el Sign Up

Para los usuarios que desean registrarse, como se explicó anteriormente, vamos a generar una contraseña aleatoria para satisfacer la obligatoriedad por parte de Cognito, pero al crear nuestro Client del User Pool, garantizamos que la autenticación sólo suceda siguiendo el flujo personalizado y nunca con usuario y contraseña.

import { Auth } from 'aws-amplify';

signUp = async event => {
  const params = {
    username: this.state.email,
    password: getRandomString(30),
    attributes: {
      name: this.state.fullName
    }
  };
  await Auth.signUp(params);
};

function getRandomString(bytes) {
  const randomValues = new Uint8Array(bytes);
  window.crypto.getRandomValues(randomValues);
  return Array.from(randomValues).map(intToHex).join('');
}

function intToHex(nr) {
  return nr.toString(16).padStart(2, '0');
}

Realizando el Sign In

Responsable de iniciar el flujo de autenticación personalizado para el usuario.

import { Auth } from "aws-amplify";

signIn = () => { 
    try { 
        user = await Auth.signIn(this.state.email);
        this.setState({ user });
    } catch (e) { 
        console.log('Oops...');
    } 
};

Respondiendo el Desafío Personalizado

En esta fase, habilitamos la cámara a través del Browser para tomar una foto y realizar su upload al S3 para finalmente informar al usuario que está disponible para su comparación.

import Webcam from "react-webcam";

// Instantiate and set webcam to open and take a screenshot
// when user is presented with a custom challenge

/* Webcam implementation goes here */


// Retrieves file uploaded to S3 and sends as a File to Rekognition 
// as answer for the custom challenge
dataURLtoFile = (dataurl, filename) => {
  var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
      bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
  while(n--){
      u8arr[n] = bstr.charCodeAt(n);
  }
  return new File([u8arr], filename, {type:mime});
};

sendChallengeAnswer = () => {

    // Capture image from user camera and send it to S3
    const imageSrc = this.webcam.getScreenshot();
    const attachment = await s3UploadPub(dataURLtoFile(imageSrc, "id.png"));
    
    // Send the answer to the User Pool
    const answer = `public/${attachment}`;
    user = await Auth.sendCustomChallengeAnswer(cognitoUser, answer);
    this.setState({ user });
    
    try {
        // This will throw an error if the user is not yet authenticated:
        await Auth.currentSession();
    } catch {
        console.log('Apparently the user did not enter the right code');
    }
    
};

Conclusión

En este Blog Post, implementamos un mecanismo de autenticación utilizando reconocimiento facial, utilizando los flujos de autenticación personalizados de Amazon Cognito y el Amazon Rekognition para la comparación facial.

Dependiendo de los criterios de seguridad de su empresa y de su aplicación, este escenario puede funcionar para usted tanto desde el punto de vista de la seguridad, como de la experiencia para el usuario.

Adicionalmente, podemos incrementar el factor de seguridad. Es posible definir una cadena de Auth Challenges compuestos no sólo por la foto del usuario, sino también de una combinación de los números del documento utilizado en el momento del registro, entre otros desafíos como MFA’s adicionales.

Como esta es una solución totalmente serverless y basada en funciones Lambda, usted puede adaptarla de acuerdo con sus necesidades. Lea más acerca de las autenticaciones personalizadas a través de nuestra guía del desarrollador.

Recursos

  • Los códigos de implementación anteriores están disponibles en GitHub. Usted puede cambiar, hacer el deploy y girar el código usted mismo.
  • Usted puede hacer el deploy de la solución directamente a través del AWS Serverless Application Repository.

Sobre el autor

Enrico Bergamo

Enrico es arquitecto de soluciones en AWS enfocado en el segmento Enterprise, y actúa ayudando a clientes de diversos segmentos en sus jornadas a la nube. Con más de 10 años de experiencia en Arquitectura y Desarrollo de Sistemas, y DevOps, Enrico actuó directamente con diversas empresas en la definición, implementación e implantación de varias soluciones corporativas.