One of the topics I’ve been meaning to write about is how to secure Function Apps with authentication to prevent unauthorized calls because a newly created Function App and its functions are by default accessible via the internet so anyone who knows the URL would be able to call it without authentication. Take one of my previous posts:
Using Azure Function App and Logic Apps to create an automated report that downloads a CSV, creates a HTML table, sends an email with the HTML table and attaches the CSV
… where I created an Azure Function that expects a POST method containing the Cylance Token it can use to download an export of all the devices, generate an HTML report, then return the content. While one can argue that even if the URL was discovered, a malicious attacker would need to know the Cylance Token to obtain any meaningful data back. This is true but a malicious attacker could also cause financial impact by calling the Azure Function repeatedly to cause unnecessary charges.
Below is the Function App:
… and the Function within the Function App that contains the PowerShell script to process the request after receiving the token:
Confirming Function App does not require authentication with Postman
To demonstrate that this function does not require authentication, we can test by using the Get Function Url:
Copy the URL:
Then open Postman or any API testing tool, create a post request with the URL:
Select No Auth for the Type under Authorization:
We’ll be passing a JSON to the Function App so navigate to Headers and uncheck Content-Type:text/plain, then add the following header:
Navigate to the Body section and paste the Cylance token for the report:
Proceed to send the request and you’ll see a Status: 200 OK with the returned HTML of the report:
Creating a System Managed Identity for the Logic App that calls the Function App
In this example, I’ll be configuring a System Managed Identity for the Logic App that is used to call the Function App to generate the report so it (the Logic App) can take the report and email it out. The System Managed Identity will be given permissions to call the Function App and any calls to it without authentication will fail. The use of System Managed Identities allows for the Logic App to authenticate with an Azure AD identity without having to provide a secret. Azure manages the identity so there are no secrets to rotate / maintain.
**Note that it is also possible to User-assigned Identity as well and the official Microsoft documentation can be found here: https://docs.microsoft.com/en-us/azure/logic-apps/create-managed-service-identity?tabs=consumption
Begin by opening the Logic App that calls the Function App, then navigate to its Identity blade:
Under the System assigned heading, switch the Status from Off to On and click on Save:
Note the message being displayed indicates that the Logic App will be registered in Azure AD so it can be granted permissions to access other resources protected by Azure AD (in this case it will be the Function App):
The process should complete in a few seconds and a Object (principal) ID will be displayed. This attribute represents the Logic App’s Managed Identity:
We’ll need the Application ID of the Logic App’s Managed Identity, which isn’t displayed in the Identity blade of the Logic App (don’t mistaken the Object Principal ID for the App ID) and it can be retrieved in the Azure AD Enterprise Application list when Application Type is configured as All Applications or Managed Identities:
Copy the Application ID value as we’ll need it to grant this Logic App permissions to the Function App.
Require Authentication for Function Calls
The first requirement to allow the Logic App’s Managed Identity to authenticate with the Function App is to set the Function App’s authentication level to anonymous. Failure to do so will result in the Logic App workflow throwing a BadRequest error.
Begin by opening the Function App, navigate to the Advanced Tools blade, then click on Go to open Kudu Services:
Click on the Debug Console menu and select CMD:
Navigate to site > wwwroot > <function name>
Edit the function.json file and look for authLevel in the bindings. If the property exists, set the property value to anonymous. Otherwise, add that property and set the value as anonymous:
The Function App in this example already has authLevel so we just need to update function to anonymous:
Clicking the Save button will bring us back to the command prompt:
Create an App Registration for the Function App in Azure AD
With the Logic App having a managed identity to authenticate against the Function App, we now need to create a App Registration for the Function App in Azure AD so it can be used to grant permissions to the managed identity of the Logic App.
We’ve already copied the Object (principal) ID for the Logic App earlier but will also need the tenant ID of the Azure AD, which can be retrieved by navigating to Azure Active Directory:
Then in the Overview blade, copy the Tenant ID:
Navigate back into the Function App and click on the Authentication blade, then the Add an identity provider button:
Select Microsoft for the Identity provider so we can use Azure AD identities:
In the App registration type section, select Provide the details of an existing app registration:
Fill in the following fields:
Application (client) ID: <The Logic App’s Application ID>
Client secret (recommended): <blank>
Issuer URL: https://sts.windows.net/<Tenant-ID>
Allowed token audiences: <The Logic App’s Application ID>
Restrict access: Require authentication
Unauthenticated requests: HTTP 401 Unauthorized: recommended for APIs
Proceed to click Add to save the settings and you should see an identity provider representing the Logic App listed in the Authentication blade of the Function App:
Confirming Function App requires authentication with Postman and Portal
Repeating the steps we performed previously with Postman should now return a Status: 401 Unauthorized and the message:
You do not have permission to view this directory or page.
Attempting to navigate to the URL of the Function App should also display the same message:
Instead of the default Your Functions 4.0 app is up and running:
Updating Logic App to authenticate with Managed Identity
Attempting to immediately trigger the Logic App after the changes will fail and return an Unauthorized response when calling the Azure Function App:
To correct the issue, enter the designer mode for the Logic App, edit the Function App step, click on Add new parameter, check the Authentication box:
Select Managed identity for the Authentication type:
Select System-assigned managed identity for Managed identity and <Logic App’s Managed Identity Application ID> as the Audience:
Proceed to run the trigger and confirm that steps succeed:
**Note that one of the common issues I’ve come across is if the Logic App’s Object (principal) ID used as the Application ID when configuring the Azure Function App’s Authentication. Doing so may cause the following error to be thrown when executing the Logic App:
BadRequest. Http request failed as there is an error getting AD OAuth token: 'AADSTS500011: The resource principal named a6486475-xxx-xxx-xxxx-6exxxxxx882f was not found in the tenant named Contoso. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant. Trace ID: a31a44e7-7135-450f-a420-aa801a8ec003 Correlation ID: 58f958c1-6a75-4fef-bc75-bb22dae4c8c9 Timestamp: 2022-09-11 20:48:23Z'.
Ensure that you had used the Application ID for the appropriate fields and this error should be resolved.
I hope this helps anyone who might be looking for a walkthrough for securing an Azure Function App and using Managed Identity for a Logic App to call the function. The Microsoft documentation today isn’t very clear to me so I hope this will provide some clarity.