Many of the Entra ID environments I work in are still full of app registrations using classic client secrets. Most of them belong to older integrations or to quick fixes nobody ever came back to. These secrets easily stay around long after they should have been retired, and in most tenants I see, nobody has a clear picture of which apps hold which secrets, when they expire or who owns them. If you want to replace client secrets, you first need to know exactly where they still exist.

Every app with a client secret goes into the inventory first

Before I replace anything, I take stock of all Entra ID app registrations that still rely on client secrets, along with their expiry dates. Microsoft Graph PowerShell handles this without much effort. This is the snippet I use to list every app registration with its active secrets and their expiry:

Get-MgApplication -All | ForEach-Object {
    $app = $_
    foreach ($secret in $app.PasswordCredentials) {
        [PSCustomObject]@{
            AppDisplayName = $app.DisplayName
            AppId = $app.AppId
            SecretDisplayName = $secret.DisplayName
            StartDate = $secret.StartDateTime
            EndDate = $secret.EndDateTime
        }
    }
} | Sort-Object EndDate

The output regularly turns up secrets that never expire, or ones that expired and nobody noticed. Both should worry you. Forgotten secrets and secrets with too many permissions are a common way in for attackers, particularly once the credentials have landed in code, build logs or documentation. Midnight Blizzard exploited exactly this kind of gap, using old app secrets to get at mailboxes and data (I wrote up my lessons from it here).

Long-lived client secrets leak into places they don't belong

My biggest issue with classic client secrets is how easily they spread. As soon as a secret value sits in a source repo, a script or an automation log, it can leak, and it may stay exposed for years. Rotating secrets across several teams or workloads is tedious, so it keeps getting postponed. If nobody wrote down who owns a secret or what it's for, old ones simply accumulate. I've come across far too many tenants where secrets were still valid years after anyone last used them.

Tip from the field: Give every app registration in Entra ID a clear owner. When a secret is close to expiry, or has already leaked, you can react much faster if you know who is responsible.

Modern alternatives replace client secrets in most scenarios

For most use cases there are now options that are both safer and easier to manage than client secrets. Which one I recommend depends on the scenario:

  • Managed identities for Azure resources: VMs, App Service or Function Apps authenticate to Azure resources with no secret or certificate at all.
  • Certificate-based authentication: certificates can be rotated securely and kept in Azure Key Vault, which works well for custom backends or daemon apps. (See also: Azure Key Vault RBAC)
  • Workload identity federation: in CI/CD setups (GitHub Actions, Azure DevOps, Kubernetes), federated credentials let external token issuers such as GitHub or ADO authenticate directly, so no secret has to be handled (Microsoft Docs).

A lot of teams are reluctant at first because they expect the migration to break their integrations. In my experience the switch usually goes more smoothly than they fear, and the security gain is considerable, above all for apps with high privileges or exposure to the internet.

Policies and clear documentation keep secrets under control

Microsoft now offers policy controls that restrict how secrets can be used. App management policies let you set a maximum secret lifetime or block new password credentials entirely (official guide). In larger environments where many developers create app registrations, this has a noticeable effect.

Something that has saved me a lot of trouble is insisting that every app registration has at least one owner and a short description of what it does. Handing over responsibility for renewing, migrating or retiring a secret then becomes simple. Orphaned apps become the exception, and you don't have to play detective when a secret expires or shows up exposed somewhere.

Workload identity federation removes the secret from the pipeline

When you plan the migration, begin with the apps that carry the most privilege or risk, meaning broad permissions or secrets that are already about to expire. For GitHub Actions or Azure DevOps deployments, federated credential support is mature by now and well documented (how to add credentials).

This simple diagram shows how authentication works with workload identity federation:

flowchart TD
    DevOpsPipeline["DevOps pipeline"] --> TokenIssuer["Token issuer (e.g. GitHub)"]
    TokenIssuer --> EntraID["Entra ID"]
    EntraID --> AppResource["Target resource (e.g. API)"]

Figure: Federated workload authentication without a client secret

I would replace client secrets now rather than later

The earlier you inventory and replace client secrets, the easier it is, and that applies most of all to the legacy integrations nobody wants to touch. New apps should only use client secrets when nothing else works. For existing apps, start moving to safer options as soon as you can. Once documentation, policy controls and ownership are in place, managing secrets gets a lot less stressful, and your Entra ID tenant ends up noticeably more secure.