Interakcja z człowiekiem w Durable Functions — przykład weryfikacji telefonu

W tym przykładzie pokazano, jak utworzyć aranżację Durable Functions, która obejmuje interakcję człowieka. Za każdym razem, gdy prawdziwa osoba uczestniczy w zautomatyzowanym procesie, proces musi mieć możliwość wysyłania powiadomień do osoby i odbierania odpowiedzi asynchronicznie. Musi również umożliwić, że dana osoba jest niedostępna. (Ta ostatnia część to miejsce, w którym przekroczenia limitu czasu stają się ważne).

Ten przykład implementuje system weryfikacji telefonu oparty na wiadomościACH SMS. Te typy przepływów są często używane podczas weryfikowania numeru telefonu klienta lub uwierzytelniania wieloskładnikowego (MFA). Jest to zaawansowany przykład, ponieważ cała implementacja jest wykonywana przy użyciu kilku małych funkcji. Nie jest wymagany zewnętrzny magazyn danych, taki jak baza danych.

Uwaga

Wersja 4 modelu programowania Node.js dla Azure Functions jest ogólnie dostępna. Nowy model w wersji 4 został zaprojektowany z myślą o bardziej elastycznym i intuicyjnym środowisku dla deweloperów języka JavaScript i TypeScript. Dowiedz się więcej o różnicach między wersjami 3 i 4 w przewodniku migracji.

W poniższych fragmentach kodu kod JavaScript (PM4) oznacza model programowania w wersji 4 , nowe środowisko.

Wymagania wstępne

Omówienie scenariusza

Weryfikacja telefonu służy do sprawdzania, czy użytkownicy końcowi twojej aplikacji nie są rozsyłani i czy są tym, kim mówią. Uwierzytelnianie wieloskładnikowe to typowy przypadek użycia ochrony kont użytkowników przed hakerami. Wyzwaniem podczas wdrażania własnej weryfikacji telefonu jest to, że wymaga stanowej interakcji z człowiekiem. Użytkownik końcowy zazwyczaj udostępnia kod (na przykład 4-cyfrowy numer) i musi odpowiedzieć w rozsądnym czasie.

Zwykłe Azure Functions są bezstanowe (podobnie jak wiele innych punktów końcowych chmury na innych platformach), dlatego tego typu interakcje obejmują jawne zarządzanie stanem zewnętrznym w bazie danych lub innym magazynie trwałym. Ponadto interakcja musi być podzielona na wiele funkcji, które można koordynować razem. Na przykład potrzebujesz co najmniej jednej funkcji do podejmowania decyzji o kodzie, utrwalaniu go gdzieś i wysyłaniu go na telefon użytkownika. Ponadto potrzebujesz co najmniej jednej innej funkcji, aby otrzymać odpowiedź od użytkownika i w jakiś sposób zamapować ją z powrotem na oryginalne wywołanie funkcji, aby przeprowadzić walidację kodu. Przekroczenie limitu czasu jest również ważnym aspektem zapewnienia bezpieczeństwa. Może szybko uzyskać dość skomplikowane.

Złożoność tego scenariusza jest znacznie ograniczona w przypadku używania Durable Functions. Jak zobaczysz w tym przykładzie, funkcja orkiestratora może łatwo zarządzać interakcją stanową i bez angażowania zewnętrznych magazynów danych. Ponieważ funkcje orkiestratora są trwałe, te interaktywne przepływy są również wysoce niezawodne.

Konfigurowanie integracji z usługą Twilio

Ten przykład obejmuje użycie usługi Twilio do wysyłania wiadomości SMS na telefon komórkowy. Azure Functions ma już obsługę usługi Twilio za pośrednictwem powiązania usługi Twilio, a przykład używa tej funkcji.

Pierwszą rzeczą, której potrzebujesz, jest konto usługi Twilio. Możesz utworzyć jedną bezpłatnie na stronie https://www.twilio.com/try-twilio. Po utworzeniu konta dodaj następujące trzy ustawienia aplikacji do aplikacji funkcji.

Nazwa ustawienia aplikacji Opis wartości
TwilioAccountSid Identyfikator SID konta usługi Twilio
TwilioAuthToken Token uwierzytelniania dla konta usługi Twilio
TwilioPhoneNumber Numer telefonu skojarzony z kontem usługi Twilio. Służy do wysyłania wiadomości SMS.

Funkcje

W tym artykule przedstawiono następujące funkcje w przykładowej aplikacji:

  • E4_SmsPhoneVerification: funkcja orkiestratora , która wykonuje proces weryfikacji telefonu, w tym zarządzanie limitami czasu i ponawiania prób.
  • E4_SendSmsChallenge: funkcja działania , która wysyła kod za pośrednictwem wiadomości SMS.

Uwaga

Funkcja HttpStart w przykładowej aplikacji i przewodniku Szybki start działa jako klient orchestration , który wyzwala funkcję orkiestratora.

funkcja orkiestratora E4_SmsPhoneVerification

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

Uwaga

Początkowo może nie być oczywiste, ale ten orkiestrator nie narusza ograniczenia orkiestracji deterministycznej. Jest to deterministyczne, ponieważ CurrentUtcDateTime właściwość jest używana do obliczania czasu wygaśnięcia czasomierza i zwraca tę samą wartość dla każdego odtwarzania w tym momencie w kodzie orkiestratora. To zachowanie jest ważne, aby upewnić się, że te same winner wyniki z każdego powtórzonego wywołania do Task.WhenAny.

Po uruchomieniu ta funkcja orkiestratora wykonuje następujące czynności:

  1. Pobiera numer telefonu, do którego będzie wysyłać powiadomienie SMS.
  2. Wywołuje E4_SendSmsChallenge , aby wysłać wiadomość SMS do użytkownika i zwraca oczekiwany 4-cyfrowy kod wyzwania.
  3. Tworzy trwały czasomierz, który wyzwala 90 sekund od bieżącego czasu.
  4. Równolegle z czasomierzem oczekuje zdarzenia SmsChallengeResponse od użytkownika.

Użytkownik otrzymuje wiadomość SMS z czterocyfrowym kodem. Mają 90 sekund, aby wysłać ten sam czterocyfrowy kod z powrotem do wystąpienia funkcji orkiestratora w celu ukończenia procesu weryfikacji. Jeśli przesyłają niewłaściwy kod, otrzymają dodatkowe trzy próby uzyskania go prawidłowo (w tym samym 90-sekundowym oknie).

Ostrzeżenie

Ważne jest , aby anulować czasomierze , jeśli nie są już potrzebne do wygaśnięcia, jak w powyższym przykładzie po zaakceptowaniu odpowiedzi na żądanie.

funkcja działania E4_SendSmsChallenge

Funkcja E4_SendSmsChallenge używa powiązania usługi Twilio do wysyłania wiadomości SMS z czterocyfrowym kodem do użytkownika końcowego.

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

Uwaga

Aby uruchomić przykładowy kod, należy najpierw zainstalować Microsoft.Azure.WebJobs.Extensions.Twilio pakiet Nuget dla usługi Functions. Nie instaluj również głównego pakietu nuget usługi Twilio , ponieważ może to powodować problemy z wersją, które powodują błędy kompilacji.

Uruchamianie aplikacji przykładowej

Korzystając z funkcji wyzwalanych przez protokół HTTP zawartych w przykładzie, można uruchomić orkiestrację, wysyłając następujące żądanie 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}"}

Funkcja orkiestratora odbiera podany numer telefonu i natychmiast wysyła wiadomość SMS z losowo wygenerowanym 4-cyfrowym kodem weryfikacyjnym — na przykład 2168. Następnie funkcja czeka 90 sekund na odpowiedź.

Aby odpowiedzieć za pomocą kodu, możesz użyć RaiseEventAsync (.NET) lub raiseEvent (JavaScript/TypeScript) wewnątrz innej funkcji lub wywołać element webhook sendEventPostUri HTTP POST, do którego odwołuje się powyższy odpowiedź 202, zastępując ciąg {eventName} nazwą zdarzenia: 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

Jeśli wyślesz to przed wygaśnięciem czasomierza, aranżacja zakończy się, a output pole zostanie ustawione na truewartość , co oznacza pomyślne zweryfikowanie.

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

Jeśli czasomierz wygaśnie lub wprowadzisz nieprawidłowy kod cztery razy, możesz wykonać zapytanie o stan i wyświetlić false dane wyjściowe funkcji orkiestracji wskazujące, że weryfikacja telefonu nie powiodła się.

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

Następne kroki

W tym przykładzie przedstawiono niektóre zaawansowane możliwości Durable Functions, zwłaszcza WaitForExternalEvent i CreateTimer interfejsów API. Wiesz już, jak można je połączyć z Task.WaitAny (C#)/ (JavaScript/context.df.Task.any TypeScript)/context.task_any (Python) w celu zaimplementowania niezawodnego systemu limitu czasu, który jest często przydatny do interakcji z prawdziwymi ludźmi. Więcej informacji na temat korzystania z Durable Functions można znaleźć w serii artykułów, które oferują szczegółowe omówienie konkretnych tematów.