Interakce člověka v Durable Functions – ukázka ověření telefonu

Tato ukázka ukazuje, jak vytvořit Durable Functions orchestraci, která zahrnuje lidskou interakci. Kdykoli je do automatizovaného procesu zapojena skutečná osoba, musí být proces schopen posílat oznámení osobě a přijímat odpovědi asynchronně. Musí také umožňovat možnost, že osoba není k dispozici. (V této poslední části jsou důležité časové limity.)

Tato ukázka implementuje systém pro ověření telefonu pomocí SMS. Tyto typy toků se často používají při ověřování telefonního čísla zákazníka nebo při vícefaktorovém ověřování (MFA). Jedná se o výkonný příklad, protože celá implementace se provádí pomocí několika malých funkcí. Není vyžadováno žádné externí úložiště dat, například databáze.

Poznámka

Verze 4 programovacího modelu Node.js pro Azure Functions je obecně dostupná. Nový model v4 je navržený tak, aby měl flexibilnější a intuitivnější prostředí pro vývojáře v JavaScriptu a TypeScriptu. Další informace o rozdílech mezi verzemi v3 a v4 najdete v průvodci migrací.

V následujících fragmentech kódu označuje JavaScript (PM4) programovací model V4, nové prostředí.

Požadavky

Přehled scénáře

Ověření pomocí telefonu se používá k ověření, že koncoví uživatelé vaší aplikace nejsou spammeři a že jsou tím, za koho říkají. Vícefaktorové ověřování je běžným případem použití ochrany uživatelských účtů před hackery. Problémem při implementaci vlastního ověření telefonu je, že vyžaduje stavovou interakci s člověkem. Koncovému uživateli se obvykle poskytne nějaký kód (například 4místné číslo) a musí odpovědět v přiměřeném čase.

Běžné Azure Functions jsou bezstavové (stejně jako mnoho dalších koncových bodů cloudu na jiných platformách), takže tyto typy interakcí zahrnují explicitní správu stavu externě v databázi nebo jiném trvalém úložišti. Kromě toho musí být interakce rozdělena do několika funkcí, které lze koordinovat společně. Například potřebujete aspoň jednu funkci pro rozhodování o kódu, jeho uložení někam a jeho odeslání na telefon uživatele. Kromě toho potřebujete alespoň jednu další funkci, abyste získali odpověď od uživatele a nějakým způsobem ji namapovat zpět na původní volání funkce, aby bylo možné provést ověření kódu. Vypršení časového limitu je také důležitým aspektem zabezpečení. Může to být poměrně složité rychle.

Při použití Durable Functions je složitost tohoto scénáře výrazně snížena. Jak uvidíte v této ukázce, funkce orchestrátoru může snadno spravovat stavovou interakci, aniž by zahrnovala externí úložiště dat. Vzhledem k tomu, že funkce orchestrátoru jsou odolné, jsou tyto interaktivní toky také vysoce spolehlivé.

Konfigurace integrace Twilio

Tato ukázka zahrnuje použití služby Twilio k odesílání zpráv SMS na mobilní telefon. Azure Functions už podporuje Twilio prostřednictvím vazby Twilio a ukázka tuto funkci používá.

První věc, kterou potřebujete, je účet Twilio. Můžete si ho zdarma vytvořit na adrese https://www.twilio.com/try-twilio. Jakmile budete mít účet, přidejte do aplikace funkcí následující tři nastavení aplikace.

Název nastavení aplikace Popis hodnoty
TwilioAccountSid Identifikátor SID vašeho účtu Twilio
TwilioAuthToken Ověřovací token pro váš účet Twilio
TwilioPhoneNumber Telefonní číslo přidružené k vašemu účtu Twilio. Používá se k odesílání zpráv SMS.

Funkce

Tento článek vás provede následujícími funkcemi v ukázkové aplikaci:

  • E4_SmsPhoneVerification: Funkce orchestrátoru , která provádí proces ověření telefonu, včetně správy časových limitů a opakování.
  • E4_SendSmsChallenge: Funkce aktivity , která odešle kód prostřednictvím textové zprávy.

Poznámka

Funkce HttpStart v ukázkové aplikaci a rychlý start fungují jako klient Orchestraation , který aktivuje funkci orchestrátoru.

funkce orchestrátoru 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;
    }
}

Poznámka

Zpočátku to nemusí být zřejmé, ale tento orchestrátor neporušuje deterministické omezení orchestrace. Je deterministický, protože CurrentUtcDateTime vlastnost se používá k výpočtu času vypršení platnosti časovače a vrací stejnou hodnotu při každém přehrání v tomto okamžiku v kódu orchestrátoru. Toto chování je důležité k zajištění stejných winner výsledků při každém opakovaném volání .Task.WhenAny

Po spuštění provede tato funkce orchestrátoru následující:

  1. Získá telefonní číslo, na které odešle oznámení SMS.
  2. Zavolá E4_SendSmsChallenge k odeslání zprávy SMS uživateli a vrátí zpět očekávaný 4místný kód výzvy.
  3. Vytvoří trvalý časovač, který aktivuje 90 sekund od aktuálního času.
  4. Paralelně s časovačem čeká na událost SmsChallengeResponse od uživatele.

Uživatel obdrží zprávu SMS se čtyřmístným kódem. Mají 90 sekund na odeslání stejného čtyřmístného kódu zpět do instance funkce orchestrátoru, aby dokončili proces ověření. Pokud odešle nesprávný kód, dostanou další tři pokusy o jeho získání (ve stejném 90sekundovém okně).

Upozornění

Pokud už nepotřebujete, aby vypršela platnost časovačů , je důležité zrušit, jako v příkladu výše při přijetí odpovědi na výzvu.

funkce aktivity E4_SendSmsChallenge

Funkce E4_SendSmsChallenge používá vazbu Twilio k odeslání zprávy SMS se čtyřmístným kódem koncovému uživateli.

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

Poznámka

Abyste mohli spustit ukázkový kód, musíte nejprve nainstalovat Microsoft.Azure.WebJobs.Extensions.Twilio balíček NuGet pro funkce. Neinstalujte také hlavní balíček NuGet Twilio , protože to může způsobit problémy se správou verzí, které vedou k chybám sestavení.

Spuštění ukázky

Pomocí funkcí aktivovaných protokolem HTTP, které jsou součástí ukázky, můžete zahájit orchestraci odesláním následujícího požadavku 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}"}

Funkce orchestrátoru obdrží zadané telefonní číslo a okamžitě mu pošle SMS zprávu s náhodně vygenerovaným 4místným ověřovacím kódem – například 2168. Funkce pak počká 90 sekund na odpověď.

Pokud chcete odpovědět kódem, můžete použít RaiseEventAsync (.NET) nebo raiseEvent (JavaScript/TypeScript) v jiné funkci nebo vyvolat webhook sendEventPostUri HTTP POST, na který odkazuje výše odpověď 202, a nahradit {eventName} názvem události: 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

Pokud ji odešlete před vypršením platnosti časovače, orchestrace se dokončí a output pole je nastavené na true, což znamená úspěšné ověření.

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

Pokud necháte vypršení platnosti časovače nebo čtyřikrát zadáte nesprávný kód, můžete se na stav dotazovat a zobrazit false výstup funkce orchestrace, která značí, že ověření telefonem selhalo.

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

Další kroky

Tato ukázka ukázala některé pokročilé možnosti Durable Functions, zejména WaitForExternalEvent a CreateTimer rozhraní API. Viděli jste, jak se dají kombinovat s Task.WaitAny (C#)/context.df.Task.any (JavaScript/TypeScript)/context.task_any (Python) a implementovat spolehlivý systém časového limitu, který je často užitečný pro interakci se skutečnými lidmi. Další informace o tom, jak používat Durable Functions, najdete v řadě článků, které nabízejí podrobné informace o konkrétních tématech.