Share via


Menselijke interactie in Durable Functions - Voorbeeld van telefoonverificatie

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

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 in de migratiehandleiding.

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

Vereisten

Overzicht van scenario

Telefoonverificatie 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 er een stateful interactie met een mens nodig is. Een eindgebruiker krijgt doorgaans code (bijvoorbeeld een getal van 4 cijfers) en moet binnen een redelijke tijd reageren.

Gewone Azure Functions zijn staatloos (net als veel andere cloudeindpunten op andere platforms), dus deze typen interacties omvatten expliciet het extern beheren van de status in een database of een ander permanent archief. Daarnaast moet de interactie worden opgesplitst in meerdere functies die samen kunnen worden gecoördineerd. U hebt bijvoorbeeld ten minste één functie nodig om een code te bepalen, deze ergens vast te houden en naar de telefoon van de gebruiker te verzenden. Daarnaast hebt u ten minste één andere functie nodig om een antwoord van de gebruiker te ontvangen en deze op 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 beveiliging te garanderen. Het kan redelijk complexer 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 tussenkomst van externe gegevensarchieven beheren. Omdat orchestratorfuncties duurzaam zijn, zijn deze interactieve stromen ook zeer betrouwbaar.

Twilio-integratie configureren

Dit voorbeeld omvat het gebruik van de Twilio-service om sms-berichten naar een mobiele telefoon te verzenden. Azure Functions biedt al ondersteuning voor Twilio via de Twilio-binding en het voorbeeld gebruikt 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. Nadat u een account hebt, voegt u de volgende drie app-instellingen toe aan uw functie-app.

Naam van de 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 die het telefoonverificatieproces uitvoert, 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 fungeert als Orchestration-client die de orchestratorfunctie 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 misschien niet duidelijk in het begin, maar deze orchestrator schendt niet de deterministische indelingsbeperking. Het is deterministisch omdat de eigenschap wordt gebruikt om de verlooptijd van de CurrentUtcDateTime timer te berekenen en deze retourneert dezelfde waarde voor elke herhaling op dit moment in de orchestratorcode. Dit gedrag is belangrijk om ervoor te zorgen dat dezelfde winner resultaten van elke herhaalde aanroep naar Task.WhenAny.

Zodra deze orchestratorfunctie is gestart, doet u 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 uitdagingscode.
  3. Hiermee maakt u een duurzame timer die 90 seconden activeert vanaf de huidige tijd.
  4. Wacht parallel met de timer op een SmsChallengeResponse-gebeurtenis van de gebruiker.

De gebruiker ontvangt een sms-bericht met een code van vier cijfers. 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 een extra drie pogingen om deze goed te krijgen (binnen hetzelfde venster van 90 seconden).

Waarschuwing

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

E4_SendSmsChallenge activiteitsfunctie

De E4_SendSmsChallenge-functie gebruikt 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 hoofd-Twilio nuget-pakket 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 orchestratorfunctie ontvangt het opgegeven telefoonnummer en verzendt het onmiddellijk een sms-bericht met een willekeurig gegenereerde verificatiecode van 4 cijfers, 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) binnen een andere functie gebruikenRaiseEventAsync of de sendEventPostUri HTTP POST-webhook aanroepen waarnaar wordt verwezen in het bovenstaande 202-antwoord, waarbij u de naam van de gebeurtenis vervangt: SmsChallengeResponse{eventName}

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 dit verzendt voordat de timer verloopt, wordt de indeling voltooid en wordt het output veld ingesteld trueop , wat aangeeft 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 een false indelingsfunctieuitvoer zien, waarmee wordt aangegeven dat de telefoonverificatie 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, met name WaitForExternalEvent en CreateTimer API's, gedemonstreerd. 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 mensen. U vindt meer informatie over het gebruik van Durable Functions door een reeks artikelen te lezen die een uitgebreide dekking van specifieke onderwerpen bieden.