Open up almost any Microsoft 365 tenant and the workload identities are usually a mess. You find old app registrations, forgotten client secrets and service principals that nobody remembers creating. I run into this in project after project. That's not really surprising, because the workload identity features in Entra ID (formerly Azure AD) have changed quickly over the last couple of years. Admins and developers alike have to rethink old habits, clear out legacy leftovers and learn what the platform can do today, and attacks on privileged app identities keep increasing.

Workload identities cover every non-human identity in Entra ID

In Entra ID, "workload identities" means all identities that don't belong to a person. If an app, script or automation uses it to reach resources, it counts. App registrations, service principals and managed identities assigned to Azure resources all fall into this group. Microsoft's workload identities overview pulls the topic together well. The problem is that app registrations and service principals are very flexible, and that flexibility makes them easy to mismanage, above all where secrets are involved. Managed identities are native to Azure. They come in two flavours. A system-assigned identity is bound to one resource and disappears when that resource is deleted. A user-assigned identity can be shared between resources. For most automation in Azure, managed identities are the better and safer option.

Client secrets remain the biggest risk in most tenants

I regularly find app registrations whose client secrets were never rotated. Sometimes those secrets have ended up in a code repo or a chat thread. A client secret is essentially a password. Whoever holds it gets every privilege the app has, and that can include broad Microsoft Graph access. When a secret leaks, the damage can reach a long way.

Certificates are better, since they allow automated rotation and are less likely to be exposed by accident. Workload identity federation goes further still, for example with GitHub Actions. You don't store or rotate any secret, because a trusted external provider handles authentication. The Workload Identity Federation guide documents the setup clearly. In short, the app (a GitHub workflow, say) presents a token that proves who it is and receives scoped, temporary access in return.

Pro tip: Rotate secrets regularly and keep them in Azure Key Vault. Better still, switch to managed identities or federation wherever you can. You'll spend less time on administration and shrink the attack surface considerably.

System-assigned and user-assigned managed identities serve different purposes

In Azure projects, it matters a lot whether you pick a system-assigned or a user-assigned managed identity. A system-assigned identity is created and removed together with its resource, so it never sticks around after its job is done, and the identity landscape stays tidy. A user-assigned identity is more flexible because several resources can share it. In some cases that simplifies permission management, but it also makes privilege creep more likely. I use system-assigned identities whenever I can, unless there's a good reason to share one.

The flowchart below shows how the different workload identity types fit together in Entra ID:

flowchart TD
  appreg["App registration"] --> sp["Service principal"]
  managedsys["Managed identity (system-assigned)"] --> sp
  manageduser["Managed identity (user-assigned)"] --> sp
  federation["Identity federation"] --> sp
  sp --> msgraph["Microsoft Graph access"]

Figure: Every type of workload identity in Entra ID ends up as a service principal that accesses Microsoft Graph.

Whichever path you take, you end up with a service principal in Entra ID. The skill lies in choosing the right identity type for each scenario and handling its lifecycle with care.

Apps need least privilege just as much as users do

One mistake I see constantly is giving apps far more application permissions than they need. I've come across simple provisioning scripts running with full Directory.ReadWrite.All rights on Microsoft Graph, which is way beyond what they require. Scope permissions strictly and check every so often which ones are actually being used. PowerShell and the Entra portal both make it easy to review app permissions. This command, for instance, lists the app registrations in your tenant that hold Directory rights:

Get-MgServicePrincipal -Filter "AppRoles/any(ar:ar.Value eq 'Directory.ReadWrite.All')"

If you take over a tenant, run checks like this on a regular schedule. There are often far more risky apps sitting unnoticed than you'd expect.

Conditional Access now covers workload identities as well

Over the past few months, Conditional Access policies for workload identities (Workload Identities Premium) have become available. You can target an app's service principal much as you would a user account, restricting access by IP, blocking risky behaviour and so on. The feature is still young, but my first results look good. It genuinely makes abuse harder, particularly after a secret has been compromised.

You do have to think about it differently. Conditional Access for users is mostly about device, location and risk. For workload identities, the focus is usually on context, meaning how the app accesses resources and from where. Setting it up takes some effort, but the security gain is well worth it.

I keep the number of self-managed app identities as small as possible

Experience has taught me that the fewer self-managed app identities, secrets and certificates you have lying around, the less trouble you'll have later. My default is managed identities, preferably system-assigned. For DevOps pipelines such as GitHub Actions, I now use federation as standard. I treat app permissions as something that keeps changing, so I review them constantly and keep them to a minimum. Start cleaning up as early as you can. You'll get better security out of it, and you'll sleep better too.