Menselijke interactie in Durable Functions - Voorbeeld van telefonische verificatie

In dit voorbeeld ziet u hoe u een Durable Functions-indeling maakt waarbij menselijke interactie is betrokken. Wanneer een echte persoon betrokken is bij een geautomatiseerd proces, moet het proces meldingen naar de persoon kunnen verzenden en asynchroon antwoorden kunnen ontvangen. Het moet ook de mogelijkheid bieden dat de persoon niet beschikbaar is. (In dit laatste deel worden time-outs belangrijk.)

In dit voorbeeld wordt een telefoonverificatiesysteem op basis van sms geïmplementeerd. Deze typen stromen worden vaak gebruikt bij het verifiëren van het telefoonnummer van een klant of voor meervoudige verificatie (MFA). Het is een krachtig voorbeeld omdat de volledige implementatie wordt uitgevoerd met behulp van een paar kleine functies. Er is geen extern gegevensarchief, zoals een database, vereist.

Notitie

Versie 4 van het Node.js programmeermodel voor Azure Functions is algemeen beschikbaar. Het nieuwe v4-model is ontworpen voor een flexibelere en intuïtievere ervaring voor JavaScript- en TypeScript-ontwikkelaars. Meer informatie over de verschillen tussen v3 en v4 vindt u in de migratiehandleiding.

In de volgende codefragmenten geeft JavaScript (PM4) het programmeermodel V4 aan, de nieuwe ervaring.

Vereisten

Overzicht van scenario's

Telefonische verificatie wordt gebruikt om te controleren of eindgebruikers van uw toepassing geen spammers zijn en of ze zijn wie ze zeggen dat ze zijn. Meervoudige verificatie is een veelvoorkomend gebruiksvoorbeeld voor het beveiligen van gebruikersaccounts tegen hackers. De uitdaging bij het implementeren van uw eigen telefoonverificatie is dat het een stateful interactie met een mens vereist. Een eindgebruiker krijgt doorgaans code (bijvoorbeeld een 4-cijferig nummer) en moet binnen een redelijke tijd reageren.

Gewone Azure Functions zijn staatloos (net als veel andere cloudeindpunten op andere platforms), dus bij dit soort interacties moet de status expliciet extern worden beheerd in een database of een ander permanent archief. Daarnaast moet de interactie worden opgesplitst in meerdere functies die met elkaar kunnen worden gecoördineerd. U hebt bijvoorbeeld ten minste één functie nodig om een code te bepalen, deze ergens op te houden en naar de telefoon van de gebruiker te verzenden. Bovendien hebt u ten minste één andere functie nodig om een antwoord van de gebruiker te ontvangen en deze op de een of andere manier weer toe te wijzen aan de oorspronkelijke functieaanroep om de codevalidatie uit te voeren. Een time-out is ook een belangrijk aspect om de beveiliging te garanderen. Het kan snel vrij complex worden.

De complexiteit van dit scenario wordt aanzienlijk verminderd wanneer u Durable Functions gebruikt. Zoals u in dit voorbeeld ziet, kan een orchestratorfunctie de stateful interactie eenvoudig en zonder externe gegevensarchieven beheren. Omdat orchestratorfuncties duurzaam zijn, zijn deze interactieve stromen ook zeer betrouwbaar.

Twilio-integratie configureren

In dit voorbeeld wordt de Twilio-service gebruikt om sms-berichten te verzenden naar een mobiele telefoon. Azure Functions heeft al ondersteuning voor Twilio via de Twilio-binding en het voorbeeld maakt gebruik van die functie.

Het eerste wat u nodig hebt, is een Twilio-account. U kunt er een gratis maken op https://www.twilio.com/try-twilio. Zodra u een account hebt, voegt u de volgende drie app-instellingen toe aan uw functie-app.

Naam van app-instelling Beschrijving van waarde
TwilioAccountSid De SID voor uw Twilio-account
TwilioAuthToken Het verificatietoken voor uw Twilio-account
TwilioPhoneNumber Het telefoonnummer dat is gekoppeld aan uw Twilio-account. Dit wordt gebruikt om sms-berichten te verzenden.

De functies

In dit artikel worden de volgende functies in de voorbeeld-app beschreven:

  • E4_SmsPhoneVerification: Een orchestratorfunctie waarmee het verificatieproces via de telefoon wordt uitgevoerd, inclusief het beheren van time-outs en nieuwe pogingen.
  • E4_SendSmsChallenge: Een activiteitsfunctie waarmee een code via een sms-bericht wordt verzonden.

Notitie

De HttpStart functie in de voorbeeld-app en de quickstart fungeren als Orchestration-client die de orchestrator-functie activeert.

E4_SmsPhoneVerification orchestratorfunctie

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

Notitie

Het is in eerste instantie misschien niet duidelijk, maar deze orchestrator schendt de deterministische indelingsbeperking niet. Het is deterministisch omdat de CurrentUtcDateTime eigenschap wordt gebruikt om de verlooptijd van de timer te berekenen en deze dezelfde waarde retourneert bij elke herhaling op dit punt in de orchestratorcode. Dit gedrag is belangrijk om ervoor te zorgen dat dezelfde winner resultaten worden geretourneerd bij elke herhaalde aanroep van Task.WhenAny.

Na het starten doet deze orchestratorfunctie het volgende:

  1. Hiermee haalt u een telefoonnummer op waarnaar de sms-melding wordt verzonden .
  2. Roept E4_SendSmsChallenge aan om een sms-bericht naar de gebruiker te verzenden en retourneert de verwachte 4-cijferige vraagcode.
  3. Hiermee maakt u een duurzame timer die 90 seconden vanaf de huidige tijd activeert.
  4. Wacht parallel met de timer op een SmsChallengeResponse-gebeurtenis van de gebruiker.

De gebruiker ontvangt een sms-bericht met een viercijferige code. Ze hebben 90 seconden om dezelfde viercijferige code terug te sturen naar het orchestratorfunctie-exemplaar om het verificatieproces te voltooien. Als ze de verkeerde code verzenden, krijgen ze nog drie extra pogingen om deze goed te krijgen (binnen hetzelfde venster van 90 seconden).

Waarschuwing

Het is belangrijk om timers te annuleren als u ze niet meer nodig hebt om te verlopen, zoals in het bovenstaande voorbeeld wanneer een antwoord op een vraag wordt geaccepteerd.

E4_SendSmsChallenge activiteitsfunctie

De functie E4_SendSmsChallenge maakt gebruik van de Twilio-binding om het sms-bericht met de viercijferige code naar de eindgebruiker te verzenden.

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

Notitie

U moet eerst het Microsoft.Azure.WebJobs.Extensions.Twilio Nuget-pakket voor Functions installeren om de voorbeeldcode uit te voeren. Installeer niet ook het Twilio nuget-hoofdpakket , omdat dit versiebeheerproblemen kan veroorzaken die leiden tot buildfouten.

De voorbeeldtoepassing uitvoeren

Met behulp van de door HTTP geactiveerde functies in het voorbeeld kunt u de indeling starten door de volgende HTTP POST-aanvraag te verzenden:

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

De orchestrator-functie ontvangt het opgegeven telefoonnummer en stuurt het onmiddellijk een sms-bericht met een willekeurig gegenereerde 4-cijferige verificatiecode, bijvoorbeeld 2168. De functie wacht vervolgens 90 seconden op een antwoord.

Als u wilt reageren met de code, kunt u (.NET) of raiseEvent (JavaScript/TypeScript) in een andere functie gebruikenRaiseEventAsync of de http POST-webhook sendEventPostUri aanroepen waarnaar wordt verwezen in het bovenstaande 202-antwoord, waarbij {eventName} u vervangt door de naam van de gebeurtenis, 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

Als u deze verzendt voordat de timer verloopt, wordt de indeling voltooid en wordt het output veld ingesteld op true, waarmee wordt aangegeven dat de verificatie is geslaagd.

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

Als u de timer laat verlopen of als u vier keer de verkeerde code invoert, kunt u een query uitvoeren op de status en de uitvoer van een false indelingsfunctie zien, die aangeeft dat de verificatie via de telefoon is mislukt.

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

Volgende stappen

In dit voorbeeld zijn enkele van de geavanceerde mogelijkheden van Durable Functions gedemonstreerd, met name WaitForExternalEvent api'sCreateTimer. U hebt gezien hoe deze kunnen worden gecombineerd met Task.WaitAny (C#)/context.df.Task.any (JavaScript/TypeScript)/context.task_any (Python) om een betrouwbaar time-outsysteem te implementeren, wat vaak handig is voor interactie met echte personen. Meer informatie over het gebruik van Durable Functions vindt u in een reeks artikelen waarin specifieke onderwerpen uitgebreid worden behandeld.