Automated secret expiration alerts with Azure Logic Apps
Automated solution to monitor App Registration secrets in Entra ID.

Expert insights on Azure AI architecture and implementation. Real-world solutions for building intelligent enterprise systems.
In this article, I will walk you through on how to create an automated solution using Azure Logic Apps to monitor App Registration secrets in Entra ID and send email notifications before they expire.
Prerequisites
Azure subscription with a Logic App already created
A managed identity with appropriate permissions to read App Registrations
Create a Managed Identity for the Logic App
First, you'll need to grant your Logic App's managed identity the necessary permissions. Assign the Microsoft Graph API permission Application.Read.All to the managed identity.
To be easier to follow, I split the design of the workflow in three parts. Let’s continue with the first.
Logic App Workflow - Foundations

Let’s look at the entry point of our Logic App. For the trigger, I use Recurrence that I set to run daily at 9 AM.

The next step is to initialize a few variables. I set a variable named DaysThreshold of type Integer with an initial value of 30. This will allow me to alert for secrets expiring within 30 days. The second variable is ExpiringSecrets which I initialize as an empty array ([]).

Add an HTTP action to fetch all app registrations. I do that by calling the HTTP GET https://graph.microsoft.com/v1.0/applications endpoint. For the authentication type use Managed Identity and the audience https://graph.microsoft.com.

Finally, use a parse JSON action to parse the response from the API. You can use the following sample schema for it:
{
"type": "object",
"properties": {
"value": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"appId": {
"type": "string"
},
"displayName": {
"type": "string"
}
},
"required": [
"id",
"appId",
"displayName"
]
}
}
}
}

That’s all the steps we need to get started with the workflow. In the second part, I will show you how to get the secrets for each app registration and check their expiry.
Logic App Workflow - Core Logic
Congratulations for making it this far. Let’s now complicate the workflow a little.

As we can see, there is a lot to unpack here.
Start off by adding a for each loop. This allows us to loop through each app registration. For the configuration, use the output from the previous step. From dynamic content, select value from the Parse JSON step.

Inside the for each loop, add another HTTP action. Inside, call the following HTTP GET endpoint https://graph.microsoft.com/v1.0/applications/@{items('For_each')?['id']}. Use the same authentication type as before, which is Managed Identity, and the same audience.

Again, use another Parse JSON action to parse the body of the response. You can use this sample JSON schema:
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"appId": {
"type": "string"
},
"displayName": {
"type": "string"
},
"passwordCredentials": {
"type": "array",
"items": {
"type": "object",
"properties": {
"customKeyIdentifier": {
"type": [
"string",
"null"
]
},
"displayName": {
"type": [
"string",
"null"
]
},
"endDateTime": {
"type": "string"
},
"hint": {
"type": [
"string",
"null"
]
},
"keyId": {
"type": "string"
},
"startDateTime": {
"type": "string"
},
"secretText": {
"type": [
"string",
"null"
]
}
}
}
},
"keyCredentials": {
"type": "array"
}
}
}

With a condition, check if the length of the passwordCredentials array is greater than zero. If it is, then we do another for each loop to go through each credential separately.


In the compose action, switch to expression tab and paste:
div(sub(ticks(items('Apply_to_each')?['endDateTime']), ticks(utcNow())), 864000000000)

Add another condition and check if the Outputs from the compose action is less than or equal to DaysThreshold we defined at the start.

If it’s true, add the expiring secret to the array we defined.
json(concat('{\"appName\":\"', body('Parse_JSON_1')?['displayName'], '\",\"appId\":\"', body('Parse_JSON_1')?['appId'], '\",\"secretName\":\"', coalesce(items('For_each_1')?['displayName'], 'No Name'), '\",\"secretHint\":\"', items('For_each_1')?['hint'], '\",\"expirationDate\":\"', items('For_each_1')?['endDateTime'], '\",\"daysRemaining\":\"', string(outputs('Compose')), '\"}'))

The most complicated part of the workflow is now complete. In the last part, we will take a look at how to send an email notification with a list of expiring secrets.
Logic App Workflow - Completion

The condition here just checks if the length of ExpiringSecrets we defined at the start is greater than 0. If it is, that means we have secrets that will expire soon!

We use the create HTML table with ExpiringSecrets to construct a simple table we will use in the email.

Final step is to add the Send an email (V2) action and send the email notification. I made the email body to be easy to read and understand:
The following App Registration secrets will expire within the next 30 days:
body('Create_HTML_table')
Please renew these secrets as soon as possible to avoid service disruption.

And that’s it. Lastly, save and test the workflow. Following is a sample email I received from my workflow (I edited out the appName, appId, secretName and secretHint).







