Compartilhar via


Interação humana nas Funções Duráveis – exemplo de verificação por telefone

Este exemplo demonstra como criar uma orquestração de Funções Duráveis que envolve interação humana. Sempre que uma pessoa real está envolvida em um processo automatizado, o processo deve ser capaz de enviar notificações para a pessoa e de receber respostas de forma assíncrona. Ele também deve contar com a possibilidade de a pessoa não estar disponível. (É nesta última parte que os tempos limite tornam-se importantes.)

Este exemplo implementa um sistema de verificação por telefone baseado em SMS. Esses tipos de fluxo costumam ser usados para verificar o número de telefone de um cliente ou para MFA (autenticação multifator). Este é um exemplo poderoso, porque toda a implementação é feita usando algumas funções pequenas. Não é necessário um armazenamento de dados externo, como um banco de dados.

Observação

A versão 4 do modelo de programação Node.js para o Azure Functions está em disponibilidade geral. O novo modelo v4 é projetado para oferecer uma experiência mais flexível e intuitiva para desenvolvedores de JavaScript e TypeScript. Saiba mais sobre as diferenças entre v3 e v4 na guia de migração.

Nos trechos de código a seguir, o JavaScript (PM4) denota o modelo de programação V4, a nova experiência.

Pré-requisitos

Visão geral do cenário

A verificação por telefone é usada para confirmar a identidade dos usuários finais de seu aplicativo e que eles não são remetentes de spam. A autenticação multifator é um caso de uso comum para proteger contas de usuário contra hackers. O desafio ao implementar sua própria verificação por telefone é que ela requer uma interação com estado com uma pessoa. Normalmente, um usuário final recebe um código (por exemplo, um número de 4 dígitos) e deve responder dentro de um período razoável.

O Azure Functions comum é sem estado (assim como muitos outros pontos de extremidade de nuvem em outras plataformas), portanto, esses tipos de interações envolvem explicitamente o gerenciamento de estado externamente em um banco de dados ou algum outro armazenamento persistente. Além disso, a interação deve ser dividida em várias funções que possam ser coordenadas juntas. Por exemplo, você precisa de pelo menos uma função para decidir sobre um código, persisti-lo em algum lugar e enviá-lo para o telefone do usuário. Além disso, você precisa de pelo menos uma outra função para receber uma resposta do usuário e, de alguma forma, mapeá-la de volta para a chamada de função original para fazer a validação do código. O tempo limite também é um aspecto importante para garantir a segurança. Mas pode ficar bastante complexo rapidamente.

A complexidade desse cenário é bastante reduzida quando você usa as Funções Duráveis. Como você verá neste exemplo, uma função de orquestrador pode gerenciar a interação com estado facilmente e sem envolver nenhum armazenamento de dados externo. Como as funções de orquestrador são duráveis, esses fluxos interativos também são altamente confiáveis.

Configurando a integração com o Twilio

Este exemplo envolve o uso do serviço Twilio para enviar mensagens SMS a um telefone celular. O Azure Functions já tem suporte para Twilio por meio da Associação com o Twilio e o exemplo usa esse recurso.

A primeira coisa de que você precisa é uma conta do Twilio. É possível criar uma gratuitamente em https://www.twilio.com/try-twilio. Quando tiver uma conta, adicione as três seguintes configurações de aplicativo ao seu aplicativo de função.

Nome da configuração do aplicativo Descrição do valor
TwilioAccountSid A SID de sua conta do Twilio
TwilioAuthToken O token de autenticação de sua conta do Twilio
TwilioPhoneNumber O número de telefone associado à sua conta do Twilio. Ele é usado para enviar mensagens SMS.

As funções

Este artigo aborda as seguintes funções no aplicativo de exemplo:

  • E4_SmsPhoneVerification: uma função de orquestrador que executa o processo de verificação de telefone, incluindo o gerenciamento de tempos e novas tentativa.
  • E4_SendSmsChallenge: uma função de atividade que envia um código por meio de mensagem de texto.

Observação

A HttpStart função no aplicativo de exemplo e o início rápido atuam como cliente de orquestração que dispara a função de orquestrador.

E4_SmsPhoneVerification função de orquestrador

[FunctionName("E4_SmsPhoneVerification")]
public static async Task<bool> Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    string phoneNumber = context.GetInput<string>();
    if (string.IsNullOrEmpty(phoneNumber))
    {
        throw new ArgumentNullException(
            nameof(phoneNumber),
            "A phone number input is required.");
    }

    int challengeCode = await context.CallActivityAsync<int>(
        "E4_SendSmsChallenge",
        phoneNumber);

    using (var timeoutCts = new CancellationTokenSource())
    {
        // The user has 90 seconds to respond with the code they received in the SMS message.
        DateTime expiration = context.CurrentUtcDateTime.AddSeconds(90);
        Task timeoutTask = context.CreateTimer(expiration, timeoutCts.Token);

        bool authorized = false;
        for (int retryCount = 0; retryCount <= 3; retryCount++)
        {
            Task<int> challengeResponseTask =
                context.WaitForExternalEvent<int>("SmsChallengeResponse");

            Task winner = await Task.WhenAny(challengeResponseTask, timeoutTask);
            if (winner == challengeResponseTask)
            {
                // We got back a response! Compare it to the challenge code.
                if (challengeResponseTask.Result == challengeCode)
                {
                    authorized = true;
                    break;
                }
            }
            else
            {
                // Timeout expired
                break;
            }
        }

        if (!timeoutTask.IsCompleted)
        {
            // All pending timers must be complete or canceled before the function exits.
            timeoutCts.Cancel();
        }

        return authorized;
    }
}

Observação

Pode não ser óbvio no início, mas esse orquestrador não viola a restrição de orquestração determinística. É determinística porque a CurrentUtcDateTime propriedade é usada para calcular o tempo de expiração do temporizador e essa propriedade retorna o mesmo valor em cada reprodução nesse ponto no código do orquestrador. Este comportamento é importante para garantir que o mesmo winner resulte de todas as chamadas repetidas para Task.WhenAny.

Uma vez iniciada, essa função de orquestrador faz o seguinte:

  1. Obtém um número de telefone para o qual enviará a notificação de SMS.
  2. Chama E4_SendSmsChallenge para enviar uma mensagem SMS para o usuário e retorna o código de desafio de 4 dígitos esperado.
  3. Cria um temporizador durável que dispara 90 segundos a partir da hora atual.
  4. Em paralelo com o temporizador, aguarda um evento SmsChallengeResponse do usuário.

O usuário recebe uma mensagem SMS com um código de 4 dígitos. Eles têm 90 segundos para enviar o mesmo código de 4 dígitos para a instância da função de orquestrador para concluir o processo de verificação. Se enviar o código errado, ele terá mais três tentativas para acertar (na mesma janela de 90 segundos).

Aviso

É importante cancelar os temporizadores se você não precisar mais que eles expirem, como no exemplo acima quando uma resposta ao desafio é aceita.

E4_SendSmsChallenge função de atividade

A função E4_SendSmsChallenge usa a associação ao Twilio para enviar a mensagem SMS com o código de 4 dígitos para o usuário final.

[FunctionName("E4_SendSmsChallenge")]
public static int SendSmsChallenge(
    [ActivityTrigger] string phoneNumber,
    ILogger log,
    [TwilioSms(AccountSidSetting = "TwilioAccountSid", AuthTokenSetting = "TwilioAuthToken", From = "%TwilioPhoneNumber%")]
        out CreateMessageOptions message)
{
    // Get a random number generator with a random seed (not time-based)
    var rand = new Random(Guid.NewGuid().GetHashCode());
    int challengeCode = rand.Next(10000);

    log.LogInformation($"Sending verification code {challengeCode} to {phoneNumber}.");

    message = new CreateMessageOptions(new PhoneNumber(phoneNumber));
    message.Body = $"Your verification code is {challengeCode:0000}";

    return challengeCode;
}

Observação

Primeiro, você deve instalar o pacote Nuget Microsoft.Azure.WebJobs.Extensions.Twilio para o Functions para executar o código de exemplo. Não instale também o pacote nuget Twilio principal, já que ele pode causar problemas de controle de versão que resultam em erros de build.

Execute o exemplo

Usando as funções disparadas por HTTP incluídas no exemplo, você pode iniciar a orquestração enviando a seguinte solicitação HTTP POST:

POST http://{host}/orchestrators/E4_SmsPhoneVerification
Content-Length: 14
Content-Type: application/json

"+1425XXXXXXX"
HTTP/1.1 202 Accepted
Content-Length: 695
Content-Type: application/json; charset=utf-8
Location: http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}

{"id":"741c65651d4c40cea29acdd5bb47baf1","statusQueryGetUri":"http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}","sendEventPostUri":"http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1/raiseEvent/{eventName}?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}","terminatePostUri":"http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1/terminate?reason={text}&taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}"}

A função de orquestrador recebe o número de telefone fornecido e envia imediatamente uma mensagem SMS para ele com um código de verificação de 4 dígitos gerado aleatoriamente, por exemplo, 2168. Em seguida, a função espera durante 90 segundos por uma resposta.

Para responder com o código, você pode usar RaiseEventAsync (.NET) ou raiseEvent(JavaScript/TypeScript) dentro de outra função ou invocar o webhook de HTTP POST sendEventUrl, referenciado na resposta 202 acima, substituindo {eventName} pelo nome do evento, SmsChallengeResponse:

POST http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1/raiseEvent/SmsChallengeResponse?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}
Content-Length: 4
Content-Type: application/json

2168

Se você enviá-lo antes que o temporizador expire, a orquestração será concluída e o campo output será definido como true, indicando uma verificação bem-sucedida.

GET http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}
HTTP/1.1 200 OK
Content-Length: 144
Content-Type: application/json; charset=utf-8

{"runtimeStatus":"Completed","input":"+1425XXXXXXX","output":true,"createdTime":"2017-06-29T19:10:49Z","lastUpdatedTime":"2017-06-29T19:12:23Z"}

Se deixar que o temporizador expire ou se inserir o código errado quatro vezes, você poderá consultar o status e ver uma saída de função de orquestração false, indicando que houve falha na verificação por telefone.

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 145

{"runtimeStatus":"Completed","input":"+1425XXXXXXX","output":false,"createdTime":"2017-06-29T19:20:49Z","lastUpdatedTime":"2017-06-29T19:22:23Z"}

Próximas etapas

Este exemplo demonstrou alguns dos recursos avançados de Durable Functions, especialmente as APIs WaitForExternalEvent e CreateTimer. Você viu como elas podem ser combinadas com Task.WaitAny (C#)/context.df.Task.any(JavaScript/TypeScript)/context.task_any para implementar um sistema de tempo limite confiável, o que geralmente é útil para interagir com pessoas reais. Você pode aprender mais sobre como usar Funções Duráveis lendo uma série de artigos que oferecem uma cobertura aprofundada de tópicos específicos.