Conditional Access Policy Hell

Why Microsoft’s “block all apps” advice still feels like a trap.

The Setup

When I first started managing Conditional Access (CA) policies, I treated them like firewall rules. Simple enough: block what you don’t want, allow what you do. But CA doesn’t work that way. Policies don’t process top down; they stack. Conflicts don’t resolve. They collide.

So, I pivoted. I tried building CA policies around controls instead of rules:

  • Multi-Factor Authentication? Required for everyone.

  • Trusted, compliant device? Required for everyone.

  • Locations? Narrowed down.

It looked clean on paper, airtight even. Until it wasn’t.

The Trap

Here’s the catch: locations aren’t controls. They’re conditions. And once you build exception logic on conditions, you have to rebuild every control around them.

That’s not a bug. That’s the design. Which brings us to my favorite kind of problem, the one that shows up mid-flight.

The Traveler

A user gets approval to work abroad. Security signs off. I create a few quick changes:

  • An exception to the existing geo-policy.

  • A new traveler-specific policy.

  • A final policy to block all other apps.

Everything lines up beautifully, least privilege, least risk. Until the traveler lands.

Their first login fails. Device non-compliant. I dig into the logs and find the issue: Two back-end apps for device management need access before compliance can be confirmed.

No problem, I think. I’ll just add those apps to the allow list. Except they’re nowhere. Not in the GUI. Not by name. Not even by GUID.

Buried in the docs is the answer: if you’re blocking all cloud apps, you need to manually add two service principals.

Import-Module Microsoft.Graph.Applications

$xplat = @{
 appId = "a0e84e36-b067–4d5c-ab4a-3db38e598ae2"
}

$tvm = @{
  appId = "e724aa31–0f56–4018-b8be-f8cb82ca1196"
}

New-MgServicePrincipal -BodyParameter $xplat
New-MgServicePrincipal -BodyParameter $tvm

After that, compliance checks work again. Unless, of course, there’s yet another hidden dependency. In that case, you repeat the ritual:

  1. Find the failed sign-in log.

  2. Copy the missing app ID.

  3. Create a new service principal.

  4. Add it to the policy.

  5. Try again.

Fun, right?

The Rant

Microsoft’s own docs warn against blocking all cloud apps because it will break things like device check-ins, background sync, and compliance updates. They know this. They even say so.

But here’s the issue: if “block all” is dangerous, make it clear in the product. Don’t bury essential service dependencies in scattered documentation. Defender visibility shouldn’t require a scavenger hunt through Graph API.

If Conditional Access truly is additive, if every policy stacks to enforce a posture, then every service dependency should be visible by default. Hide nothing. Let administrators build confidently, not by trial, error, and missed flights.

The Takeaway

Conditional Access isn’t a firewall; it’s a web of context and control. That’s powerful when used right but painful when hidden dependencies keep tripping defenders.

Until Microsoft surfaces every critical app dependency up front, the rest of us will keep spelunking through Conditional Access Policy Hell, one PowerShell command at a time.

Previous
Previous

You’re a Developer, All Right. But You’ll Never Be a Supervillain.