How I used Azure Functions at Arctic Cloud Developer Challenge 2020This is my second post about the tech I used during the Arctic Cloud Developer Challenge 2020. I'm going to explain how I secured my Azure functions and used managed identities to call them from other apps.
What are Azure Functions
Azure Functions are small, serverless pieces of code which provides you with a way to build services without having to think about infrastructure. It allows you to write code in a myriad of languages and execute them based on predefined triggers. They support nuget and NPM dependencies and can be scaled and deployed with a simple click of a button, making them extremely powerful and easy to administrate.
Functions run in an application service, the same way a web application, logic apps, et.al. does, and you can also deploy stateful versions called durable functions, perfect for orchestrating messages which run over prolonged periods of time.
When should I use them?I generally use them for everything that can't be easily handled by a logic app and doesn't need a Gui. Though functions are awesome combined with a static website I would not spend the time and effort to build a front-end based on Azure Functions. For that we have Blazor </buzzword>
Why should I use them?Whenever you can, I would say. They can be developed, tested and published extremely quick, and if you follow the recommended design principles they are easy to replace and upgrade when needed. The biggest weakness they have is versioning, but handling this can be made very easy by putting Azure API Management or a CDN in front. Like all pieces of software versioning is painful to the N-th degree, where N is the number of versions you need to support. They do not replace the need for a proper software architecture, they just make the actual development and deployment very easy.
Securing Azure Functions
For my vote handling system I had some authenticated functions that performed some of the simple operations, which was receiving new votes. The reason why I used an Azure Function as opposed to a Logic App was that I had some business rules that needed to be validated (read: manipulation system) and was easier to fix in code than logic apps (also, I wanted to code).
The code was very easy, just accept a post request, manipulate the score based on a small ruleset, then update a database with the data (see my next post for using role based authentication with SQL databases), return OK.
I wanted to trigger this function from a Logic App, and I wanted it to be secured, so I had 3 options to do this
Function Key (aka API key)This is my least favorite authentication mechanism for functions. It simply creates an API key which is infinitely reusable. It's added as a query parameter to support GET requests. It works fine, but unless you have old legacy systems I can't think of any reason to use this over app service authentication.
Custom implementationThis is what I ended up doing with my public facing APIs. I created my own version of API keys and let people authenticate using those. The keys were stored in a separate database and I used an intermediate function app to gain access to them.
App service authenticationApp service authentication enables you to specify an authentication provider for your apps. It's a point-and-click way to secure your web apps in Azure and I highly recommend it for your authentication scenarios. This is what I ended up enabling for internal facing APIs
Using app service authentication for Azure Functions
To enable app service authentication go to the platform features tab, then click on Authentication/Authorization to open up the app service authentication blade. Enable the setting to get access to the different configuration properties. After it has been switched on you have 5 different providers to choose from for authentication; Azure AD, Microsoft, Facebook, Google and Twitter. I only opted for the AAD version. Additionally you can choose what to do by default if an unauthenticated request is retrieved, in which case I selected to opt for AAD auth. You can also choose to accept anonymous access, which will enable you to use the same app for both anonymous and authenticated access.
Opening the azure active directory settings there are a few parameters that should be set. I chose the advanced setting, and used the following settings
- ClientID, randomly generated Guid
- Issuer Url, collected from Azure AD, go to App Registrations, click on the endpoints button. Copy one of the URLs with a Guid and remove the trailing directory, example: https://login.microsoftonline.com/9ac742b1-1aa1-858f-7f4f-10ae04274891
- Client Secret, blank.
- Allowed Token Audiences, something meaningful, I used https://votehandling.azurewebsites.net
This gives all authenticated AAD resources permissions to access the resource, so if you want to limit based on Groups or Role access you need to do that inside your code. I did not need to use it, but you have access to the User object in your code so you can take it from there.
The result was that I now could authenticate using SMI from my logic app (blog post here).
Using custom app keys
For my public facing API I wanted to try my hand at creating a custom system for verifying app keys. What I did was create one anonymous function to get an app key, then another to validate that app key. When a key was requested a valid email address had to be submitted, and I generated a key consisting of two concatenated Guids. I then sent a request to a logic app, using SMI authentication, and then the logic app added a row in a SQL database with the generated key and the email address. Next the logic app would send an email to the specified email address, with a link to verify the app key. This link pointed to the other anonymous function. When that function was called it would trigger another logic app, which changed a column value for that app key from 'requested' to 'validated'.
I created a new anonymous function which took in a Vote object as well as an API key parameter. This function then used MSI to call a back-end function, submitting the API key and vote object. Finally, this back-end function validated the key in the back-end, and registered the new vote.