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:
- Obtém um número de telefone para o qual enviará a notificação de SMS.
- Chama E4_SendSmsChallenge para enviar uma mensagem SMS para o usuário e retorna o código de desafio de 4 dígitos esperado.
- Cria um temporizador durável que dispara 90 segundos a partir da hora atual.
- 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.