For all developers that are using logic app http triggers, here is a brief solution of how we can secure logic apps a little more.

This is a solution without Azure Api Management or making use of proxies to hide the signature from the url and posting it as a header.

In my case I had a scheduled function app that did some data processing and posted the result to a logic app. For this example we will create a downsized/stripped solution of this. To achieve this we need to create the following things:

  • A logic app
  • App registration
  • A scheduled function app

Let's dive right in and create a simple logic app that only has a http trigger.

logicapp

After saving the logic app an url will be created and it will look like:

https://prod-41.westeurope.logic.azure.com:443/workflows/1dd2822de5034e543088be263d4dd412/triggers/manual/paths/invoke?api-version=2016-10-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=0qwAP5sOqZ6aA4As8eGjZipRiLRYOTuwnHuaMjwEMGb

Don't worry, this http trigger isn't live anymore.

Now anyone with this url can trigger the logic app. Therefore it would be wise not to share this or store it.. Wait what? How are you supposed to call it then? We can retrieve the url from the logic app resource. But in order to do that we need to create a app registration first.

App registration

In order to solve this problem we need to create an app registration on the Active Directory (AD) and subscription where your logic app is created. This can be done by going to http://aka.ms/registeredappsprod or by navigating to https://portal.azure.com > Azure Active Directory > App registration. Just create a new one with no permissions. Also the redirect url can be left empty. Before we can use it, we need to generate a new client secret.

newsecret-1

Also add this app with "Reader" role to the same subscription as your logic app. You can do this by going to 'Access Control' of your subscription and adding a new role assignment

accesscontrol

Create a scheduled function app

To make use of the just created app registration we can create a scheduled function app that looks like this:

[FunctionName("Scheduled_ProcessData")]
public static async Task Run([TimerTrigger("0 */1 * * * *")]TimerInfo myTimer, ILogger log)
{
    // Do something here
}

For this example we used a function app but since access to the logic app resource is just a REST call you can access it from any solution that is able to do REST calls.

Secure it a little

Now we got the basic plumbing done What we can do now is, instead of retrieving the url from a config or from a keyvault, retrieve the url from the logic app (resource)!

In the code below we will do the following things in order:

  • Setup all the values need to execute the calls
  • Construct the url from where we can retrieve the logic app url
  • Retrieve the logic app url (which is not the same url you see in when editing the app)
  • Post a message to this new url

For this code to work you need to add the nuget for "Microsoft.Azure.Management.ResourceManager.Fluent;" and insert the using for it.

// Setup 
var tenantId = "6a76fe50-e9d9-477f-9831-e43347c8f9d4";
var clientId = "a66fcb38-c768-4afc-ab79-d544d2e147d0";
var secret = "your app password/secret here";
var subscriptionId = "554395c0-759d-4299-9134-554e608f7f68";
var resourceGroupName = "Your Azure Resource Group here";
var logicAppName = "Your Logic app name here";

// Setup resource client
var creds = new AzureCredentialsFactory().FromServicePrincipal(clientId, secret, tenantId, AzureEnvironment.AzureGlobalCloud);
var restClient = RestClient.Configure()
    .WithEnvironment(AzureEnvironment.AzureGlobalCloud)
    .WithBaseUri("https://management.azure.com/")
    .WithCredentials(creds)
    .Build();
            
// Construct meesage
var url = $"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Logic/workflows/{logicAppName}/triggers/manual/listCallbackUrl?api-version=2016-06-01";
var message = new HttpRequestMessage(HttpMethod.Post, url);

// Add authentication headers on message
await restClient.Credentials.ProcessHttpRequestAsync(message, new System.Threading.CancellationToken(false));

// Retrieve logic app url
var httpClient = new HttpClient();
var httpResponse = await httpClient.SendAsync(message);
var apiResponse = await httpResponse.Content.ReadAsStringAsync();

// Send Message to logic app
var authenticatedUrl = (string)JsonConvert.DeserializeObject<dynamic>(apiResponse).value;
await httpClient.PostAsync(authenticatedUrl, new StringContent("Message to logic app"));

Let me first state that the hardcoded values in the setup is a real 'no go'. If you want to use this example please get them from a KeyVault. This can be done by adding the Managed Identity in the Access Policies of the KeyVault and using the KeyVault SDK. For simplicity I kept hardcoded values. A more detailed explanation can be found here

For now I just send an StringContent to the logic app but of course, you can send anything that the logic app is able to receive. Now run the function and wait for it to trigger a run. When the function has been executed you can see we successfully posted a message to the logic app.

postedToLogicApp

The url will stay valid until the primary access key of the logic app is regenerated.

Secure it even a little further

Even though the url's to call the logic app are retrieved on runtime and not saved, the static url is still available. I must admit that the chance of that url leaking somewhere or being brute-forced is very small. To limit this even more is regenerating it every x minutes/hours/days. We can do this by basically calling the same code but with a different url and we need to add some content to the post.

Change the url to:

https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Logic/workflows/{logicAppName}/regenerateAccessKey?api-version=2016-06-01

And add this 1 row of code to add content to the message you post:

message.Content = new StringContent(JsonConvert.SerializeObject(new { keyType = "Primary" }), Encoding.UTF8, "application/json");

The 'keyType' property can be set to "Primary" or "Secondary" which will then regenerate the specified key. This is the same functionality as in the azure portal.

azureportalaccesskeys

A nice added feature is that when changing the primary key all the retrieved logic app urls will be invalid after 10 minutes or so.

When changing the access key, keep in mind that you probably need to refresh the azure portal and wait a few minutes. It occurred a several times that, when opening the logic app runs without the portal refresh, an error was show about not being authorized to view the content like below:

unauthorized

Well, that is it! Now you have a solution with rolling keys for logic apps and a way to access your logic apps without configuring a URL. Of course, you have to be realistic and wonder what the change is that someone guesses/brute-force an URL and signature.. that would be kinda "remarkable". But if you are the person that wants all the extra security possible then this is maybe for you :)

Thanks for reading!