Unlock Your Microsoft 365 Tenant after Conditional Access misconfiguration in One Click

Have you ever been locked out of your Microsoft 365 tenant due to a misconfigured Conditional Access policy ?

If yes, you probably know how painful it is to rely on Microsoft support to regain access especially when time is critical.

Traditionally, the answer has been to use a Break Glass account. While this is still a recommended best practice, Break Glass accounts come with their own set of risks and challenges:

  • They must be carefully configured and excluded from all policies
  • They need to be tested regularly
  • Their credentials must be securely stored and monitored

But what if there was a modern, automated solution that restores access to your tenant with a single click ?

In this article, I’ll show you how to build a fully automated Azure Runbook that can :

  • ✅ Exclude a specific user from all Conditional Access policies using Microsoft Graph
  • ✅ Be triggered instantly through an HTTP request
  • ✅ Save your admin team hours and reduce dependency on manual escalation

Let’s get started 👇

Watch technical steps video from here :

https://www.youtube.com/watch?v=hV__pEY2v9M

Get my PowerShell Script repository Link from here : https://github.com/aymenjaz/PowerShell/blob/main/1%20-%20Microsoft%20365/Azure/Automation%20Account/Exclude%20user%20from%20All%20Conditional%20Access%20Policies.md


1 – The Goal

We’ll build an Azure Automation Runbook that :

  • Authenticates to Microsoft Graph using an app registration
  • Takes a user UPN as input
  • Automatically excludes that user from all Conditional Access policies
  • Can be triggered via HTTP with a secured webhook

2 – How it works ❓

3 – Step-by-Step Guide


Step 1 : Register an Entra ID Application

  1. Go to Microsoft Entra ID → App registrations
  2. Click “+ New registration”
  1. Name your app (e.g., M365-CAPolicy-Recovery)
  2. Choose Single tenant
  3. Click “Register”

Let’s move to Client secret creation.

Step 2 : Create a Client Secret

  1. In the App Registration → Certificates & secrets
  2. Click “New client secret”
  3. Set an expiry (e.g., 24 months)
  4. Click “Add
  • Copy the secret immediately and store it securely , it will be used in your Runbook script.

Keep in mind you should replace the secret every 2 years.

Let’s Grant Microsoft Graph API Permissions now.

Step 3 : Grant Microsoft Graph API Permissions

  1. Go to API PermissionsAdd a permission
  2. Choose Microsoft Graph
  3. Select Application permissions
  4. Select “Microsoft Graph
Contenu de l’article
  • Select Application Permission

Select and add these roles :

  • Click “Grant admin consent” for the tenant
Contenu de l’article

Step 4 : Create an Azure Automation Account

  1. Go to the Azure Portal
  2. Search for “Automation Accounts”
  1. Click “+ Create”
  1. Fill in the required info (Subscription, Resource Group, Name, Region)
  2. Click “Create”
  • Here Automation account is created successfully.

Let’s create the runbook now.

Step 5 : Create PowerShell Runbook

  1. In your Automation Account → Runbooks
  2. Click “+ Create a runbook”
  1. Name it (e.g., Exclude-User-From-CA)
  2. Choose PowerShell version 7.2
  3. Create
  1. Paste the following script (script provided below)
  2. Replace the following parameters : Tenant ID, Entra Application ID, Entra Application Scret
  3. The script will take a UserToExclude as input :
# User to exclude from all CA Policies
param(
    [object]$WebhookData
)

# Extraire JSON body from Webhook request
if ($WebhookData -ne $null) {
    $body = $WebhookData.RequestBody | ConvertFrom-Json

    # Get UserToExclude
    $UserToExclude = $body.UserToExclude
    Write-Output "User to exclude : $UserToExclude"
}
else {
    Write-Error "WebhookData is null"
    exit
}


# ----------------------------
# Define your app credentials
# ----------------------------
$TenantId     = "xxxxxx-xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxx"  # entra tenant ID
$ClientId     = "xxxxxx-xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxx"  # entra application ID
$ClientSecret = "xxxxxx-xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxx"   # entra application secret

$Scope        = "https://graph.microsoft.com/.default"
$AuthUrl      = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"

# ========== Build Body ==========
$Body = @{
    client_id     = $ClientId
    scope         = $Scope
    client_secret = $ClientSecret
    grant_type    = "client_credentials"
}

# ========== Request Access Token ==========
$TokenResponse = Invoke-RestMethod -Method POST -Uri $AuthUrl -Body $Body -ContentType "application/x-www-form-urlencoded"
$AccessToken   = $TokenResponse.access_token

# ========== Use Access Token in API Call ==========
$Headers = @{
    Authorization = "Bearer $AccessToken"
}

# ===============================
# GET USER OBJECT ID
# ===============================
$UserResponse = Invoke-RestMethod -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$UserToExclude" -Headers $Headers
$UserId = $UserResponse.id

# ===============================
# GET ALL CONDITIONAL ACCESS POLICIES
# ===============================
$PoliciesResponse = Invoke-RestMethod -Method GET -Uri "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" -Headers $Headers
$Policies = $PoliciesResponse.value

# ===============================
# EXCLUDE USER FROM ALL CONDITIONAL ACCESS POLICIES
# ===============================
foreach ($Policy in $Policies) {
    Write-Output "Processing policy: $($Policy.displayName)"

    $Conditions = $Policy.conditions
    if (-not $Conditions.users) {
        Write-Output "Policy has no user condition, skipping..."
        continue
    }

    $IncludeUsers = $Conditions.users.includeUsers
    $ExcludeUsers = $Conditions.users.excludeUsers

    if ($ExcludeUsers -contains $UserId) {
        Write-Output "User already excluded, skipping..."
        continue
    }

    # Add the user to the exclusion list
    $NewExcludeList = $ExcludeUsers + $UserId

    # Reconstruct the conditions object
    $NewConditions = @{
        users = @{
            includeUsers = $IncludeUsers
            excludeUsers = $NewExcludeList
        }
    }

    # Update the policy
    $UpdateUri = "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/$($Policy.id)"
    $Body = @{
        conditions = $NewConditions
    } | ConvertTo-Json -Depth 10

    Invoke-RestMethod -Method PATCH -Uri $UpdateUri `
    -Headers @{
        Authorization = "Bearer $AccessToken"
        "Content-Type" = "application/json"
    } `
    -Body $Body

    Write-Output "User excluded from policy: $($Policy.displayName)"
}

# ===============================
# REMOVE TOKEN
# ===============================
Remove-Variable AccessToken, ClientSecret, TokenResponse, Headers, PatchHeaders -ErrorAction SilentlyContinue

Step 6 : Create a Webhook

  1. Open the Runbook → Add webhook
  • Click Webhook
  1. Set Webhook Name and expiration date (e.g., 1 year)
  2. Copy the URL (store it securely because it cannot be recovered later)
  3. Click OKPublish the runbook
  • Click on “Parameters and run settings” , dont change anything then click OK
  • Click Create

Step 7 : Test the Solution

You can now trigger the recovery from any HTTP tool (Postman, Power Automate, custom portal) by calling:

# Define User to Exclude from Conditional Access
$UserToExclude = "User@domain.com"

# Replace this with your actual webhook URL
$webhookUrl = "https://690328.webhook.yq.azure-automation.net/webhooks?"

# Send HTTP POST request
Invoke-RestMethod -Uri $webhookUrl -Method Post -Body (@{ UserToExclude = $($UserToExclude) } | ConvertTo-Json) -ContentType 'application/json' 

The Runbook will then:

  • Authenticate to Microsoft Graph using the app secret
  • Retrieve the user object ID
  • Loop through all Conditional Access policies
  • Exclude that user from each one

Here is the result :

4 – Rethinking the Break Glass Account

This solution brings up an important question:

Do we still need a Break Glass account ?

While Break Glass accounts are a best practice, they require:

  • Credential rotation
  • Exclusion from security monitoring
  • Manual access procedures

In contrast, the solution above is :

  • Secure (uses app-based auth and controlled webhook access)
  • Automated
  • Testable and maintainable

💡 Why not have both and let this solution act as a modern, managed safety net for Conditional Access recovery?


5 – Conclusion

With this approach, you can:

  • Quickly recover admin access without external dependency
  • Automate a critical recovery step securely
  • Simplify your security posture without compromising control

Thanks

Aymen EL JAZIRI (Microsoft MVP)
Aymen EL JAZIRI (Microsoft MVP)

Hi, I’m Aymen El Jaziri , a passionate System Administrator and Microsoft MVP, with years of hands-on experience in managing and securing modern IT infrastructures.
This blog is where I share technical guides, automation scripts, product reviews, and real-world solutions that help IT professionals simplify their day-to-day work and stay ahead in a fast-evolving cloud ecosystem.
Whether you’re here to troubleshoot an issue, improve your automation game, or learn new best practices , welcome in my blog !
Let’s build a stronger, smarter IT community together.
Feel free to connect with me on LinkedIn for more content, discussions, or collaboration opportunities.

Thanks

Aymen

Articles: 154