O blog da AWS

Autenticando suas aplicações através de reconhecimento facial utilizando o Amazon Cognito e o Amazon Rekognition

Com o passar do tempo, acaba sendo notório o aumento do uso de diferentes aplicativos, redes sociais, plataformas financeiras, e-mails, plataformas de armazenamento em nuvem, etc. E com isso, gerenciar diferentes senhas e credenciais pode se tornar um desafio um tanto quanto doloroso.

Em muitos casos, compartilhar uma única senha através de todos estes aplicativos e plataformas não é uma realidade. Padrões de segurança diferentes são exigidos, composição apenas com caracteres numéricos, políticas de renovação de senha, fora a simples questão de segurança.

Mas e se pudéssemos ampliar as formas de usuários se autenticarem em suas aplicações de uma maneira mais simples, conveniente, e acima de tudo, segura?

Este artigo demonstra como utilizar o Amazon Cognito user pools para customizar seus fluxos de autenticação e permitir login em suas aplicações com o auxílio do Amazon Rekognition para reconhecimento facial através de uma aplicação simples.

Visão Geral da Solução

Ao realizar o cadastro de um novo usuário em uma aplicação Mobile ou Web, além de definir um e-mail, solicitamos que o usuário faça o upload de um documento com foto. Utilizando o Amplify Framework, podemos facilmente integrar nossa aplicação Front-End com o Amazon S3 e armazenar esta imagem em um bucket seguro e criptografado. Uma função Lambda será disparada para cada nova imagem dentro deste bucket para que possa indexar a imagem dentro do Amazon Rekognition a gravar em uma tabela do DynamoDB seus metadados para consultas posteriores.

Para autenticação, esta solução utiliza o Amazon Cognito user pools com funções Lambda para customizar os fluxos de autenticação em conjunto com a API de CompareFaces do Amazon Rekognition para identificar o nível de assertividade entre a imagem passada no momento do cadastro do usuário.

O fluxo acima descreve os passos percorridos pela solução:

  1. O usuário realiza o Sign Up cadastrando seus dados no User Pool.
  2. O usuário anexa no momento do Sign Up uma foto de um documento com nome e foto, por exemplo, um passaporte, que é carregado para um S3 Bucket.
  3. Uma função Lambda é disparada com os dados da imagem carregada no bucket.
  4. A função indexa a imagem dentro da Collection específica de documentos de usuários criada no Amazon Rekognition.
  5. A mesma função, após a indexação, persiste em uma tabela do DynamoDB os metadados da imagem indexada, juntamente com o e-mail cadastrado no Cognito user pool para consultas posteriores.
  6. O usuário entra um e-mail na página de Sign In, que envia a solicitação para o Cognito user pools.
  7. O user pool invoca a função de “Define Auth Challenge”, que determina qual desafio customizado deverá ser criado no momento.
  8. O user pool invoca a função de “Create Auth Challenge”, que consulta no DynamoDB pelos dados do usuário o dono do e-mail informado para recuperar os dados da imagem indexada no Rekognition.
  9. O user pool invoca a função de “Verify Auth Challenge”, que verifica se o challenge anterior foi de fato completado com sucesso, e caso tenha encontrado a imagem, compara com a foto tirada no momento do Sign In para validar a assertividade na comparação entre as duas imagens.
  10. O user pool invoca novamente a função “Define Auth Challenge”, que verifica que o desafio foi respondido com sucesso e que não existem novos desafios a serem criados. Ele inclui um atributo de “issueTokens:true” no seu payload de resposta para o user pool. O user pool pode se considerar autenticado agora, e envia para o usuário um JSON Web Token (JWT) (resposta para o passo 6).

Aplicação Serverless

A implementação a seguir está disponibilizada como uma aplicação serverless. Você pode fazer sua implantação a partir do AWS Serverless Application Repository.

As principais partes desta aplicação são:

  • Obrigatório que os usuários cadastrarem um e-mail válido.
  • O App Client do Cognito User Pool está configurado como “Only allow custom authentication” já que quando um usuário se cadastra, o Amazon Cognito requer uma senha para este usuário. Vamos criar uma senha randômica para este usuário, visto que não queremos que ele faça Sign In com esta senha posteriormente.
  • Dois S3 Buckets: um para armazenar as imagens de documentos carregados no momento do Sign Up, e um para armazenar as imagens passadas no momento do Sign In para comparações.
  • As seguintes funções Lambda que implementam o indexamento das imagens no Amazon Rekognition e customizam os desafios de autenticação no Amazon Cognito User Pools:
    • Create Rekognition Colletion (Python 3.6) – Está função Lambda é disparada apenas uma vez no início da implementação, para criar uma Custom Collection no Amazon Rekognition, que servirá como indexador para os documentos carregados no momento do Sign Up de cada usuário.
    • Index Faces (Python 3.6) – Esta função Lambda é disparada para cada novo upload de documento no Bucket S3 selecionado, e se responsabiliza por indexar este documento na collection do Amazon Rekognition e persistir seus metadados no DynamoDB.
    • Define Auth Challenge (Node.js 8.10) – Esta função Lambda rastreia o fluxo de autenticação customizado, que se compara ao de uma função “decider” de uma máquina de estado. Ela determina qual desafio deverá ser criado para usuário e em qual ordem. No final, ela reporta de volta para o user pool se o usuário passou ou não na autenticação. Esta função é invocada no início do fluxo de autenticação, e também após o término de cada função “Verify Auth Challenge Response”.
    • Create Auth Challenge (Node.js 8.10) – Esta função Lambda é invocada baseada na instrução da função “Define Auth Challenge”, para criar um desafio único para o usuário. Usaremos esta função para pesquisar no DynamoDB se existem registros para o usuário em questão e se seus metadados são válidos.
    • Verify Auth Challenge Response (Node.js 8.10) – Esta função Lambda é invocada pelo user pool quando o usuário provê uma resposta para o desafio criado. Seu trabalho é simplesmente validar se sua resposta está correta. Neste caso, comparar as imagens passadas nos momentos do Sign Up e Sign In usando a API de CompareFaces do Amazon Rekognition e, se o nível de assertividade for maior que 90%, considera como resposta válida.

Criando uma Collection no Amazon Rekognition

Esta função é responsável apenas por criar uma Collection no Amazon Rekognition para receber imagens de documentos com fotos de usuários no momento do 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 Imagens no Amazon Rekognition

Esta função é responsável por receber os dados e indexá-la na Collection do Amazon Rekognition e persistir seus metadados na tabela do 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

Função Define Auth Challenge

Esta função é responsável por gerenciar o fluxo de autenticação do User Pool. No array de session (event.request.session), está presente todo estado do fluxo de autenticação.

Se ela estiver vazia, o fluxo de autenticação acabou de começar. Se ela possuir itens, o fluxo está em andamento: um desafio foi apresentado ao usuário, o usuário apresentou uma resposta, e foi verificada se está correta ou errada. Em qualquer um dos casos, esta função decide o que fazer em seguida.


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;
}

 

Função Create Auth Challenge

Esta função busca no DynamoDB por um registro contendo o e-mail providenciado pelo usuário e recupera o ID do seu objeto dentro da Collection do Amazon Rekognition, e define como desafio que o usuário forneça uma foto que referencie a mesma pessoa.

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;
}

Função Verify Auth Challenge Response

Esta função é responsável por verificar no Amazon Rekognition se é possível encontrar uma imagem com assertividade igual ou acima de 90% com a imagem enviada no momento do Sign In, e se a imagem faz referência de fato a mesma pessoa que o usuário alega ser através do e-mail passado.

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;
}

Aplicação Front-End

Para coordenar , é necessário criar uma página de Sign In customizada. Você pode usar o AWS Amplify Framework para integrar sua página de Sign In com o Amazon Cognito e o upload de fotos com o Amazon S3.

O Amplify Framework permite que você implemente suas páginas utilizando seu framework favorito (React, Angular, Vue, HTML/JavaScript, etc). Os exemplos abaixo podem ser customizados para satisfazer suas necessidades específicas.

O trecho abaixo demonstra como importar e inicializar o 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 o Sign Up

Para usuários que desejam se cadastrar, conforme explicado acima, iremos gerar uma senha randômica para satisfazer a obrigatoriedade por parte do Cognito, porém ao criar nosso Client do User Pool, garantimos que a autenticação somente aconteça seguindo o fluxo customizado, e nunca com usuário e senha.

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 o Sign In

Responsável por iniciar o fluxo de autenticação customizado para o usuário.

import { Auth } from "aws-amplify";

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

Respondendo o Desafio Customizado

Nesta fase, habilitamos a câmera através do Browser para tirar uma foto e realizar seu upload para o S3 para enfim informar ao usuário que ela está disponível para comparação.

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');
    }
    
};

Conclusão

Neste Blog Post, nós implementamos um mecanismo de autenticação utilizando reconhecimento facial, utilizando os fluxos de autenticação customizados do Amazon Cognito e o Amazon Rekognition para comparação facial. Dependendo dos critérios de segurança da sua empresa e da sua aplicação, este cenário pode funcionar para você tanto do ponto de vista de segurança, quanto de experiência para o usuário.

Adicionalmente, podemos incrementar o fator de segurança, é possível definir uma cadeia de Auth Challenges compostos não só pela foto do usuário, mas também de uma combinação dos números do documento utilizado no momento do cadastro, entre outros challenges como MFA’s adicionais.

Como esta é uma solução totalmente serverless e baseada em funções Lambda, você pode customizá-la de acordo com suas necessidades. Leia mais sobre autenticações customizadas através do nosso guia do desenvolvedor.

Recursos

  • Os códigos da implementação acima estão disponíveis no GitHub. Você pode alterar, fazer o deploy e rodar o código você mesmo.
  • Você pode fazer o deploy da solução diretamente através do AWS Serverless Application Repository.

Enrico Bergamo

Enrico é arquiteto de soluções na AWS focado no segmento Enterprise, e atua auxiliando clientes de diversos segmentos em suas jornadas para a nuvem. Com mais de 10 anos de experiência em Arquitetura e Desenvolvimento de Sistemas, e DevOps, Enrico atuou diretamente com diversas empresas na definição, implementação e implantação de diversas soluções corporativas.