In real Entra ID environments, Conditional Access policies turn into a tangle of exceptions, urgent fixes and outdated documentation surprisingly fast. At some point nobody can say who changed what, when or why. Making sure changes get reviewed and are safe shouldn't depend on manual notes or someone's memory. This is where Conditional Access as code helps. Once I treat policies as code, with export, versioning, pull requests and automated deployment, I get oversight and consistency back, even as the policy set grows and changes.
Every setup starts with a policy export through Microsoft Graph PowerShell
The first step is to export all Conditional Access policies in a structured format. I use the Microsoft Graph PowerShell SDK for this (you need the Security Administrator or Global Administrator role, plus the Policy.Read.All permission) and run the following in PowerShell 7:
# PowerShell 7, Microsoft.Graph module installed
Connect-MgGraph -Scopes "Policy.Read.All"
Get-MgIdentityConditionalAccessPolicy | ConvertTo-Json -Depth 10 > .\ConditionalAccessPolicies.json
The result is a JSON file with every policy detail, and I commit it to a Git repository. If you want to automate more with Graph, have a look at my post Automating Microsoft 365 with the Microsoft Graph PowerShell SDK: Life after AzureAD and MSOnline. Once the policies live in Git, each change is a commit that goes through a pull request. Quiet edits nobody knows about and documentation that never got updated stop being a problem.
Git takes over versioning, review and drift detection for Conditional Access
From here on, the Git repository is the source of truth for Conditional Access. I review every pull request and catch risky misconfigurations early, such as the wrong user group, an exclusion that's far too broad or a policy accidentally set to "off". I can also always trace who changed what, when and why. Drift, meaning the production tenant no longer matches the repo, is easy to detect: I schedule a fresh export and compare it with main. Azure Pipelines or GitHub Actions handle that recurring check without much effort.
My tip: Don't skip drift detection. In a lot of tenants, admins make one-off tweaks directly in the portal and never update the documentation. With regular exports and Git diffs, I see these unapproved changes immediately and can follow up on them.
Pipelines deploy changes through workload identity federation, starting in report-only
Rolling out policy changes shouldn't mean clicking through the portal. My pipelines (Azure DevOps or GitHub Actions) authenticate to Entra ID with workload identity federation. There are no client secrets involved, which reduces the risk of leaked credentials. The pipelines run PowerShell scripts that deploy the policy changes through Graph (API reference).
One rule I always follow: new or changed policies go out in report-only mode first. That lets me check the impact in the logs before any block is enforced in production. Once I've validated the effect and run my tests, I switch the policy to "on". For automated checks before rollout, the open-source tool Maester works well for policy linting and best practice validation, and it spares you a fair amount of trouble later.
The Entra audit logs still record every policy change
Even with everything managed as code, I still rely on the classic audit logs. In the Entra admin center → Monitoring & health → Audit logs (category: Policy), I can see who created, edited or deleted Conditional Access policies and when. That's where I go first when a rollout causes problems or a change shows up that I didn't expect. The audit logs also support automated alerts in case an unplanned policy change gets through.
This diagram shows how Conditional Access as code works in practice, from the tenant export through review to deployment:
flowchart LR
Entra["Entra ID Tenant"] --> Export["Export Policies (Graph PowerShell)"]
Export --> Git["Git Repository"]
Git --> Review["Pull Request Review"]
Review --> Pipeline["Pipeline Deployment"]
Pipeline --> Entra
Pipeline --> Test["Report-only, Testing"]
Git --> Drift["Drift Detection"]
Figure: CA as code at a glance, with export, review, deployment and drift detection.
Discipline pays off even in smaller tenants
Conditional Access as code isn't only something for large enterprises. Smaller tenants get the same benefits: fewer accidental changes, documentation that doesn't go stale and much quicker troubleshooting. Exporting with Microsoft Graph PowerShell, keeping the policies in Git and deploying through a pipeline (with workload identity federation) have proven themselves in my day-to-day work. If you're still putting together your first policies, start with my earlier post Conditional Access in Microsoft Entra ID: The First Policies Every Tenant Needs, and then go a step further with Conditional Access as code.


