Azure Course Labs

Durable Functions: Human Interaction

An advantage of durable functions is that they can wait for an extended period of time for an activity to complete, and if that activity contains sensitive data it’s not stored outside of the function so it’s much harder for attackers to get to it. This is perfect for human interaction, where a workflow executes up to a point and then stops, waiting for human input. This lets you build a fully automated workflow but with a human approval step.

In this lab we’ll use a durable function with the Twilio binding, to send an SMS text message to a user. The function waits for the user to reply before continuing.

Reference

Pre-requisites

Twilio is a service for sending SMS messages. It has a free tier with an allowance which is plenty for development and prototyping. There are a few setup steps we need to do:

Start by setting up a Twilio account - https://www.twilio.com/try-twilio

You will need to use:

From your account page open API keys & tokens and make a note of your authentication details - keep these secure:

Twilio auth token

Now you’ll need to create a Twilio phone number - this is the number that will show as the sender when you send messages from Azure functions.

The charge for this will come from your free credits

Twilio buy number

Make a note of your new number and you’re ready to go!

HTTP Trigger with Orchestration

The scenario is a simple two-factor authentication, where a text message gets sent to the use with a code and they need to input the code to continue. We’ll just use the standard HTTP calls we get with the orchestration trigger, but these can easily be wrapped up in a nice web UI.

The code is in the 2FA folder:

Twilio is only used to send the message to a phone number. The function needs to be sent a status update with the user’s response code, and it is the function logic which determines if the user is authenticated.

Test the function locally

There are no dependencies for this function other than the standard Storage Account.

Run Docker Desktop and start the Azure Storage emulator:

docker run -d -p 10000-10002:10000-10002 --name azurite mcr.microsoft.com/azure-storage/azurite

You will need the local configuration file with your Twilio details, so create a text file at labs/functions-durable/human/2FA/local.settings.json and add the standard settings. Use E.164 formatting - so the number starts with a plus sign, then the country code, then the number - e.g. +447412972480.

{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet",
        "TwilioAccountSid": "<your-twilio-account-SID>",
        "TwilioAuthToken": "<your-twilio-auth-token>",
        "TwilioPhoneNumber": "<the-twilio-phone-number-you-bought>"
    }
}

Make sure you don’t acidentally push the JSON file to GitHub. Twilio have bots monitoring all public repos, and if they find authentication details anywhere they’ll regenerate your auth token and send you an email of shame.

Run the function locally:

cd labs/functions-durable/human/2FA

func start

You’ll see the usual startup logs, with the functions listed.

Make an HTTP POST request to start the workflow - use your own mobile number so Twilio will send the SMS message from the number you bought to your own number and use E.164 formatting.

E.g. the UK international dialling code is 44, so if my number was 07654 123123 I would use +447654123123:

# use `curl.exe` on Windows
curl -XPOST http://localhost:7071/api/Authenticate?number=+447654123123

You should get a text message with a four-digit code (how exciting!), and you should see logs like this:

[2022-11-14T16:13:49.544Z] Starting SmsChallenge for: + 44xxx
[2022-11-14T16:13:49.549Z] Executed 'SmsVerify' (Succeeded, Id=8feb5637-523c-46c4-9fae-262beab6da05, Duration=14ms)
[2022-11-14T16:13:49.586Z] Executing 'SmsChallenge' (Reason='(null)', Id=3d9aa8a2-8c3a-4ef0-807f-6f853e44c90c)
[2022-11-14T16:13:49.587Z] Sending verification code 4091 to + 44xxx.

Now you need to confirm the code you got by sending a status update to the function. The response from your curl request to the HTTP trigger included a field with a URL for raising events to the orchestration instance:

"sendEventPostUri":"http://localhost:7071/runtime/webhooks/durabletask/instances/eb9fa85442254eb8af7de25efaca5dda/raiseEvent/{eventName}?taskHub=TestHubName&connection=Storage&code=_umg_d2m6RKVVzHbDKM9xmWQjIkhVazcg01c5nKIlMxGAzFulTbm8Q=="

Use that URL with the eventName set to SmsChallengeResponse to make a curl request to confirm your code from the SMS message:

# use curl.exe on Windows
curl -XPOST -d <your-sms-code>  -H 'Content-Type: application/json' "http://localhost:7071/runtime/webhooks/durabletask/instances/<id-from-url>/raiseEvent/SmsChallengeResponse?taskHub=TestHubName&connection=Storage&code=<code-from-url>"

This is a bit fiddly and it may take a few goes to get the syntax right. You have five minutes :)

You will see in the function logs whether your authentication code is correct, or if you don’t respond in time:

[2022-11-14T16:13:50.184Z] Executed 'SmsVerify' (Succeeded, Id=eda56e78-a907-496f-81b3-fab326cd0785, Duration=6ms)
[2022-11-14T16:14:25.583Z] Executing 'SmsVerify' (Reason='(null)', Id=51088b64-54f3-40b7-9069-61b48ea596a8)
[2022-11-14T16:14:25.584Z] Starting SmsChallenge for: + 44xxx
[2022-11-14T16:14:25.585Z] Authorized! User responded correctly to SmsChallenge for: + 44xxx

You can also call the URL in the statusQueryGetUri field to check on the status. The output will read true if you authenticated correctly.

It’s worth deploying this in Azure so you can test it out, but you may be disappointed with the UX for working with the orchestration :)

Deploy to Azure

This is the setup for your Function App:

az group create -n labs-functions-durable-human --tags courselabs=azure -l eastus

az storage account create -g labs-functions-durable-human --sku Standard_LRS -l eastus -n <sa-name>

az functionapp create -g labs-functions-durable-human  --runtime dotnet --functions-version 4 --consumption-plan-location eastus --storage-account <sa-name> -n <function-name> 

You will need to set your Twilio details in the Function App Settings (use the same values from your local settings JSON):

There are no dependencies - no external services are used for triggers or bindings, so you can go right ahead and deploy:

func azure functionapp publish <function-name>

Try the function by using the test feature in the HTTP trigger - you’ll need to add your phone number as a parameter. The response includes the usual URLs to check on the status and send an event, but there is nothing helpful in the Portal to invoke those calls, you still need to build up the URL and use curl.

Lab

You need to use the HTTP trigger for human interaction functions so that you get the status workflow where consumers can post events - like when the user has entered their code. Ordinarily that would all be taken care of in your web UI, but how can you design it so that the website doesn’t need to keep polling the status endpoint to see if the user is authroized?

Stuck? Try my suggestions ___

Cleanup

Stop the Azure Storage emulator:

docker rm -f azurite

Delete the lab RG:

az group delete -y --no-wait -n labs-functions-durable-human