Security Azure

Zero Trust on Microsoft Entra: Conditional Access + PIM, Step by Step

Zero Trust is three principles: verify explicitly, use least-privilege access, and assume breach. On Microsoft Entra ID, those translate directly into Conditional Access (CA) policies and Privileged Identity Management (PIM). This guide builds a defensible baseline you can ship to production.

Zero Trust access flow

The Conditional Access mental model

Every sign-in is evaluated against your CA policies. A policy is if (signals) then (grant/block with controls):

The golden rules: start in report-only, always exclude a break-glass account, and layer narrow policies rather than one mega-policy.

Step 0 — Break-glass accounts (do this first)

Create two cloud-only *.onmicrosoft.com global admin accounts with long random passwords, excluded from every CA policy, monitored by an alert. They are your way back in if a CA policy locks everyone out.

Step 1 — Named locations

Define trusted egress IPs (offices, VPN) so policies can treat “outside known networks” as higher risk.

Entra admin center → Protection → Conditional Access → Named locations
  + IP ranges location: "Corp Egress"  → 203.0.113.0/24 (Mark as trusted)
  + Country location:    "Allowed Countries" → your operating countries

Step 2 — The baseline policy set

Build these as separate policies (each in report-only first, then On):

CA01 — Require MFA for all users

CA02 — Block legacy authentication

CA03 — Require compliant or hybrid-joined device for admins

CA04 — Risk-based (requires Entra ID P2)

CA05 — Require app protection policy on mobile

Create CA01 via Microsoft Graph PowerShell

Connect-MgGraph -Scopes "Policy.ReadWrite.ConditionalAccess","Policy.Read.All"

$params = @{
  displayName = "CA01 - Require MFA for all users"
  state       = "enabledForReportingButNotEnforced"   # report-only first
  conditions  = @{
    users        = @{ includeUsers = @("All"); excludeUsers = @("<break-glass-object-id>") }
    applications = @{ includeApplications = @("All") }
  }
  grantControls = @{ operator = "OR"; builtInControls = @("mfa") }
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $params

Run report-only for 1–2 weeks, review Insights & reporting for would-be impact, then flip state to enabled.

Step 3 — Least privilege with PIM

Standing global admins are the #1 audit finding. PIM makes privileged roles eligible instead of active: an engineer activates a role just-in-time, with MFA, justification, approval, and a time limit.

Configure a role (e.g., Global Administrator):

Entra admin center → Identity Governance → PIM → Entra roles → Roles → Global Administrator
  Role settings:
    Activation max duration:        2 hours
    Require MFA on activation:       Yes
    Require justification:           Yes
    Require approval to activate:    Yes  → approvers: "Security Leads"
    Require ticket information:      Yes
  Assignments:
    Add "Platform Engineers" as ELIGIBLE (not Active)

Activate JIT (self-service):

PIM → My roles → Entra roles → Global Administrator → Activate
  duration: 1h · justification: "INC-4821 firewall rule" · (MFA challenge) → pending approval

Now your tenant can have zero standing global admins. Access is granted for exactly as long as the work takes, fully logged.

Step 4 — Access reviews

Schedule quarterly access reviews on privileged roles and high-value groups so eligibility doesn’t rot:

Identity Governance → Access reviews → New
  Review: PIM eligible "Global Administrator"
  Reviewers: role's managers / Security Leads
  Recurrence: Quarterly · Auto-apply results: Yes · If no response: Remove access

Step 5 — Verify & monitor

Rollout checklist

Enterprise scenario

A platform team rolled out PIM and set Require approval to activate on Global Administrator with “Security Leads” as approvers. Two weeks later, a Sev1 hit at 02:00: Exchange Online routing was broken, the on-call SRE needed Global Admin, and every approver was asleep. Activation sat in PendingApproval for 40 minutes. The post-incident finding was blunt: they had traded standing access for an availability dependency on a human.

The fix was to stop putting break-glass and emergency paths through the approval gate. We kept approval on the eligible day-to-day assignment but created a separate, alert-wired emergency path: a dedicated cloud-only account holding the role as Active assignment in PIM (no activation step), excluded from CA, with a Sentinel analytics rule firing on any sign-in.

The subtle gotcha is the PIM activation hierarchy: a role can be eligible and active simultaneously, and Active assignments bypass activation controls entirely — including approval and MFA-on-activation (the account still hits MFA via CA). We also dropped the auth-context requirement on the break-glass exclusion so a misconfigured policy couldn’t strand it.

# Emergency: permanent Active assignment, no approval gate
$params = @{
  action           = "adminAssign"
  accessId         = "member"
  principalId      = "<break-glass-object-id>"
  roleDefinitionId = "62e90394-69f5-4237-9190-012177145e10" # Global Administrator
  directoryScopeId = "/"
  scheduleInfo     = @{ startDateTime = (Get-Date); expiration = @{ type = "noExpiration" } }
}
New-MgRoleManagementDirectoryRoleAssignmentScheduleRequest -BodyParameter $params

The principle held — zero standing admins for humans doing normal work — without making 02:00 recovery depend on someone answering their phone.

Common pitfalls

Zero Trust isn’t a product you buy — it’s the steady removal of implicit trust. Conditional Access verifies every request; PIM ensures privilege is temporary. Together they cover “verify explicitly” and “least privilege”; pair them with device compliance (Intune) and network segmentation (your landing zone) for “assume breach.”

Zero TrustConditional AccessPIMEntra IDIAMMFA

Comments

Keep Reading