Entra app registrations with client secrets are IAM users with a Microsoft logo. They sit in Key Vault, in GitHub variables, in a Function's configuration, and in a screenshot from last rotation. The project we run is not "remember to rotate." It is replace Azure client secrets with managed identity for every caller that already lives on Azure, then shrink the leftover secret population to the few callers that cannot use a managed identity yet.
Microsoft's managed identities overview is the platform contract: Azure hosts a credential, Entra issues a token, there is no secret you copy. What the docs will not do is inventory every passwordCredential in the tenant or decide which Function still needs a cert because it calls a partner who only speaks client secrets.
Replace Azure Client Secrets With Managed Identity: Inventory First
We start with Microsoft Graph, not a wiki.
# Entra app registrations with password credentials still live
apps = graph.get("/applications?$select=id,appId,displayName,passwordCredentials,keyCredentials")
for app in apps:
for secret in app.get("passwordCredentials") or []:
findings.append({
"app": app["displayName"],
"app_id": app["appId"],
"secret_id": secret["keyId"],
"expires": secret.get("endDateTime"),
"owners": graph.owners(app["id"]),
"sign_ins": graph.sp_signins(app["appId"]), # last used, if logged
})
Each row gets a home:
- Azure-hosted caller — App Service, Functions, Container Apps, AKS workload identity, VMs, VMSS, Automation. These move to system-assigned or user-assigned managed identity.
- CI caller — GitHub or GitLab talking to Azure. These move to OIDC federation on the app registration (federated credentials), not a new secret. Different article family; same elimination goal.
- Off-Azure caller — a partner, an on-prem job, a SaaS that can only present
client_id+ secret. These stay on a certificate if the other side allows it, or a named exception with an owner and an expiry.
If you skip inventory, teams "migrate the Function we remember" and leave three app registrations that still have a secret in a Logic App.
Move Azure-Hosted Callers to Managed Identity
DefaultAzureCredential already prefers managed identity in Azure. The work is IAM, not a new SDK:
- Enable a managed identity on the compute (system-assigned when one app owns one identity; user-assigned when several functions share a client).
- Grant that identity the RBAC the secret used to buy — Key Vault get, Storage blob, SQL Azure AD auth, Service Bus. Copy the effective role, not Contributor "so it works."
- Point configuration at identity-based endpoints (
DefaultAzureCredential, Azure Identity libraries). RemoveAZURE_CLIENT_SECRETfrom settings. - Confirm sign-in logs show the managed identity's object id, not the app registration's client-secret grant.
- Delete the password credential on the app registration. If the registration exists only as a secret holder, retire it.
User-assigned identities are the right shape when a slot swap or a scale set should keep the same principal. System-assigned dies with the resource, which is what we want for a one-off Function. We do not create one user-assigned identity for the whole subscription and call it least privilege.
This is the Azure sibling of workload identity federation on AWS. Same idea: the platform attests the caller. You stop minting a password.
What Still Needs a Secret (and How We Shrink That)
Not every caller is Azure-hosted. A vendor that requires client_secret in a form post will not take a managed identity token. For those we:
- Prefer a certificate credential on the app registration over a password. Rotation is still required; the secret is not sitting in a pipeline variable in plaintext as often.
- Prefer federated credentials if the caller can OIDC (GitLab, GitHub, another Entra tenant).
- If neither works, the leftover password is a tracked exception: owner, last used, expiry, ticket to revisit. It is not "the way we do Azure."
On-prem jobs that call Azure look a lot like on-prem jobs that call AWS. On the AWS side we use IAM Roles Anywhere. On the Azure side we use cert-based app credentials or a hybrid identity pattern the tenant already supports. We will not invent a fake managed identity for a box Azure cannot attest.
Cutover Without an Outage Story
Dual-run: the Function accepts both the managed identity and the old secret for one deploy window. Then we revoke the secret and watch 401s. If a forgotten second caller still used the secret, inventory missed it — that is why Graph sign-in logs and Key Vault access logs matter before delete.
Key Vault referenced secrets are a trap. Removing the app setting is not enough if a slot or a staging instance still resolves @Microsoft.KeyVault(...). We grep configuration and ARM/Bicep/Terraform for the secret name, not just the production slot UI.
Evidence the Sample Wants
- Graph export of app registrations without
passwordCredentialsfor the in-scope apps - Resource identities listed (principal ids) and the RBAC assignments that replaced the secret
- Sign-in logs showing client-credential grants dropped to the named exceptions
- The exception list itself, with owners
Rotate long-lived AWS credentials is the same control story in another cloud: rotation is a bridge. Deletion is the outcome. We do not claim a Microsoft or SOC 2 certification for doing this. We shrink the secret population the auditor will sample.
This cutover is security automation on identity, with the compute wiring sitting next to AWS cloud infrastructure when the estate is mixed. Production SaaS platforms we run already treat standing cloud passwords as a defect. Azure gets the same rule.
Talk to us about your credential elimination pilot: rutagon.com/contact · 907-841-8407 · contact@rutagon.com.
Frequently Asked Questions
Should we use system-assigned or user-assigned managed identity?
System-assigned when one resource has one identity and you want it gone when the resource is gone. User-assigned when several resources must share a principal (slot swaps, scale sets, a shared Key Vault policy). We do not share one user-assigned identity across unrelated apps.
Can GitLab or GitHub keep using an Entra client secret?
They should not. Those callers can attach a federated credential to the app registration and present an OIDC token. That is the same family as GitLab OIDC into AWS, pointed at Entra instead of STS.
What if our Function still needs to call a vendor that only accepts client secrets?
That vendor path stays on a named secret or cert, owned and expiry-tracked. The Function's Azure calls (Key Vault, Storage, SQL) still move to managed identity. One leftover secret is not an excuse to keep five.
Does deleting the client secret break local developer debugging?
Locally, developers use Azure CLI or Visual Studio credentials through DefaultAzureCredential, not the production secret copied to a laptop. If local still requires the prod secret, that is a finding we fix in the same engagement.
How do we prove to an auditor that the secrets are gone?
A dated Graph extract with empty passwordCredentials for in-scope apps, plus sign-in logs that no longer show those client-secret grants, plus the exception register for anything that remains.