Part 7: Main application API endpoint
Previous part: Main app startup code
The app's /api/v1/getcode API generates a JSON response that contains an alphanumerical code and a timestamp.
First, the @app.route decorator tells Flask that the get_code function handles requests to the /api/v1/getcode URL.
@app.route('/api/v1/getcode', methods=['GET'])
def get_code():
Next, we call the third-party API, the URL of which is in number_url, providing the access key that we retrieve from the key vault in the header.
headers = {
'Content-Type': 'application/json',
'x-functions-key': access_key
}
r = requests.get(url = number_url, headers = headers)
if (r.status_code != 200):
return "Could not get you a code.", r.status_code
The x-functions-key property in the header is specifically how Azure Functions (where this example third-party API is deployed) expects an access key to appear in a header. For more information, see Azure Functions HTTP trigger - Authorization keys. If calling the API fails for any reason, we return an error message and the status code.
Assuming that the API call succeeds and returns a numerical value, we then construct a more complex code using that number plus some random characters (using our own random_char function).
data = r.json()
chars1 = random_char(3)
chars2 = random_char(3)
code_value = f"{chars1}-{data['value']}-{chars2}"
code = { "code": code_value, "timestamp" : str(datetime.utcnow()) }
The code variable here contains the full JSON response for the app's API, which includes the code value as well as a timestamp. An example response would be {"code":"ojE-161-pTv","timestamp":"2020-04-15 16:54:48.816549"}.
Before returning that response, however, we write a message with it in our storage queue using the Queue client's send_message method:
queue_client.send_message(code)
return jsonify(code)
Processing queue messages
Messages stored in the queue can be viewed and managed through the Azure portal or with the Azure CLI command az storage message get. The sample repository includes a script (test.cmd and test.sh) to request a code from the app endpoint and then check the message queue. There's also a script to clear the queue using the az storage message clear command.
Typically, an app like this example would have another process that asynchronously pulls messages from the queue for further processing. As mentioned earlier, the response generated by this API endpoint might be used elsewhere in the app with two-factor user authentication. In that case, the app should to invalidate the code after a certain period of time, say 10 minutes. A simple way to do this task would be to maintain a table of valid two-factor authentication codes, which are used by its user login procedure. The app would then have a simple queue-watching process with the following logic (in pseudo-code):
pull a message from the queue and retrieve the code.
if (code is already in the table):
remove the code from the table, thereby invalidating it
else:
add the code to the table, making it valid
call queue_client.send_message(code, visibility_timeout=600)
This pseudo-code employs the send_message method's optional visibility_timeout parameter, which specifies the number of seconds before the message becomes visible in the queue. Because the default timeout is zero, messages initially written by the API endpoint become immediately visible to the queue-watching process. As a result, that process stores them in the valid code table right away. By queuing the same message again with the timeout, the process knows that it will receive the code again 10 minutes later, at which point it removes it from the table.
Implementing the main app API endpoint in Azure Functions
The code shown previously in this article uses the Flask web framework to create its API endpoint. Because Flask needs to run with a web server, such code must be deployed to Azure App Service or to a virtual machine.
An alternate deployment option is the serverless environment of Azure Functions. In this case, all the startup code and the API endpoint code would be contained within the same function that's bound to an HTTP trigger. As with App Service, you use function application settings to create environment variables for your code.
One piece of the implementation that becomes easier is authenticating with Queue Storage. Instead of obtaining a QueueClient object using the queue's URL and a credential object, you create a queue storage binding for the function. The binding handles all the authentication behind the scenes. With such a binding, your function is given a ready-to-use client object as a parameter. For more information and example code, see Connect Azure Functions to Azure Queue Storage.
Next steps
Through this example, you've learned how apps authenticate with other Azure services, and how apps can use Azure Key Vault to store any other necessary secrets for third-party APIs.
The same pattern demonstrated here with Azure Key Vault and Azure Storage applies with all other Azure services. The crucial step is that you set the correct role permissions for the app within that service's page on the Azure portal, or through the Azure CLI. (See How to assign role permissions). Be sure to check the service documentation to learn whether you need to configure any other access policies.
Always remember that you need to assign the same roles and access policies to any service principal you're using for local development.
In short, having completed this walkthrough, you can apply your knowledge to any number of other Azure services and any number of other external services.
One subject that we haven't touched upon here is authentication of users. To explore this area for web apps, begin with Authenticate and authorize users end-to-end in Azure App Service.