Entra ID Conditional Access Blocking Service Principal or Managed Identity
Fixing Service Principal blocked by Conditional Access: Sign-in Logs analysis, excluding workload identities from policies, configuring Workload Identity Premium.
This runbook covers diagnosing and fixing service principal blocking by Conditional Access after a Zero Trust rollout. For a comprehensive guide on identity management strategy, see our article Azure AD and identity management - how to implement Zero Trust. If you need help configuring Conditional Access and workload identities - book a consultation.
Symptom
Service principal or managed identity authorisation fails with the error AADSTS53003: Access has been blocked by Conditional Access policies. CI/CD pipelines, Terraform plan/apply, az CLI scripts, and Microsoft Graph API calls stop working:
# Próba logowania jako service principal kończy się błędem
az login --service-principal -u <app-id> -p <secret> --tenant <tenant-id>
# ERROR: AADSTS53003: Access has been blocked by Conditional Access policies.
# Trace ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# Sprawdź Sign-in Logs dla service principali (wymaga roli Global Reader lub Security Reader)
# Logowania nieinteraktywne (service principals) - osobna sekcja w Entra ID
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/auditLogs/signIns?\$filter=signInEventTypes/any(t: t eq 'servicePrincipal') and status/errorCode eq 53003&\$top=10&\$orderby=createdDateTime desc" \
--query "value[].{app:appDisplayName, appId:appId, time:createdDateTime, policy:appliedConditionalAccessPolicies[0].displayName, result:appliedConditionalAccessPolicies[0].result}" -o table
# Alternatywnie: sprawdź logi dla konkretnego service principal (appId)
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/auditLogs/signIns?\$filter=appId eq '<app-id>' and status/errorCode eq 53003&\$top=5&\$orderby=createdDateTime desc" \
--query "value[].{time:createdDateTime, resource:resourceDisplayName, ip:ipAddress, policy:appliedConditionalAccessPolicies[].displayName, failure:status.failureReason}" -o json
In the results you can identify the name of the CA policy that blocked the sign-in. The appliedConditionalAccessPolicies[].result field shows failure for the policy enforcing the block.
# Sprawdź szczegóły konkretnej polityki Conditional Access
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" \
--query "value[?displayName=='Zero Trust - Require MFA All Apps'].{id:id, name:displayName, state:state, conditions:conditions, grantControls:grantControls}" -o json
Root Cause
Conditional Access blocks service principals after a Zero Trust rollout for several reasons:
-
Policies targeting “All cloud apps” include service principals: New CA policies with condition
includeApplications: AllorincludeUsers: Allalso apply to non-interactive sign-ins (service principal sign-ins). In “All users” mode, service principals are NOT excluded by default - Entra ID treats them as subjects governed by the policy. -
MFA or compliant device requirement is impossible for machine identities to satisfy: A service principal has no physical device and cannot perform multi-factor authentication. A policy requiring
grant: mfaorgrant: compliantDeviceautomatically blocks every non-interactive sign-in. -
New “Workload Identities” policies without proper exclusions: Since 2023, Entra ID allows creating CA policies dedicated to workload identities. If the security team deployed such policies with location conditions (Named Locations) or risk conditions without accounting for CI/CD agent IPs - sign-ins from GitHub Actions or Azure DevOps hosted agents are blocked.
-
Named Locations condition blocks sign-ins from CI/CD agent IPs: GitHub Actions agents, Azure DevOps hosted agents, and other CI systems use dynamic cloud IP addresses. A CA policy restricting access to “trusted locations” blocks sign-ins from these addresses because they are not within the defined IP ranges.
-
“Block legacy authentication” policy catches OAuth2 client credentials flow: Some policies blocking legacy auth (Basic, IMAP, SMTP) inadvertently cover modern client credentials flows too, if the filtering conditions are not precise.
Resolution
A) Exclude service principals from CA policies targeting users:
Service principals should be excluded from policies intended for human users. Use excludeServicePrincipals or the clientAppTypes condition:
# Pobierz ID polityki, która blokuje
POLICY_ID=$(az rest --method GET \
--url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" \
--query "value[?displayName=='Zero Trust - Require MFA All Apps'].id" -o tsv)
# Sprawdź aktualną konfigurację wykluczeń
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/$POLICY_ID" \
--query "{users:conditions.users, apps:conditions.applications, clientApps:conditions.clientAppTypes}" -o json
# Dodaj wykluczenie service principali przez filtr clientAppTypes
# Ogranicz politykę TYLKO do logowań interaktywnych (browser + mobile apps)
az rest --method PATCH \
--url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/$POLICY_ID" \
--headers "Content-Type=application/json" \
--body '{
"conditions": {
"clientAppTypes": ["browser", "mobileAppsAndDesktopClients"]
}
}'
# Alternatywnie: wyklucz konkretne service principali z pola users.excludeServicePrincipals
# (wymaga licencji Workload Identity Premium)
az rest --method PATCH \
--url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/$POLICY_ID" \
--headers "Content-Type=application/json" \
--body '{
"conditions": {
"users": {
"excludeServicePrincipals": ["<service-principal-object-id-1>", "<service-principal-object-id-2>"]
}
}
}'
Note: The
excludeServicePrincipalsfield requires Workload Identity Premium licensing ($3/identity/month). Without this licence, use theclientAppTypescondition as a workaround.
B) Create a dedicated CA policy for Workload Identities:
Rather than excluding SPs from user policies - create a separate policy targeting workload identities with appropriate conditions:
# Utwórz Named Location dla IP runnerów CI/CD
az rest --method POST \
--url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/namedLocations" \
--headers "Content-Type=application/json" \
--body '{
"@odata.type": "#microsoft.graph.ipNamedLocation",
"displayName": "CI/CD Runners - GitHub Actions and Azure DevOps",
"isTrusted": true,
"ipRanges": [
{"@odata.type": "#microsoft.graph.iPv4CidrRange", "cidrAddress": "4.148.0.0/14"},
{"@odata.type": "#microsoft.graph.iPv4CidrRange", "cidrAddress": "13.64.0.0/11"},
{"@odata.type": "#microsoft.graph.iPv4CidrRange", "cidrAddress": "20.36.0.0/14"}
]
}'
# Pobierz ID nowej Named Location
LOCATION_ID=$(az rest --method GET \
--url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/namedLocations" \
--query "value[?displayName=='CI/CD Runners - GitHub Actions and Azure DevOps'].id" -o tsv)
# Utwórz politykę CA dla workload identities - blokuj logowania spoza zaufanych lokalizacji
az rest --method POST \
--url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" \
--headers "Content-Type=application/json" \
--body '{
"displayName": "Workload Identity - Block untrusted locations",
"state": "enabledForReportingButNotEnforced",
"conditions": {
"clientApplications": {
"includeServicePrincipals": ["All"]
},
"applications": {
"includeApplications": ["All"]
},
"locations": {
"includeLocations": ["All"],
"excludeLocations": ["'$LOCATION_ID'", "AllTrusted"]
}
},
"grantControls": {
"operator": "OR",
"builtInControls": ["block"]
}
}'
Important: Always start with
state: enabledForReportingButNotEnforced(report-only mode). Only switch toenabledafter validation in Sign-in Logs.
C) Use Managed Identity instead of Service Principal:
Managed Identity (system-assigned or user-assigned) bypasses most CA policies because the sign-in occurs internally within Azure without flowing through Entra ID Conditional Access:
# Dla Azure DevOps - włącz Managed Identity na self-hosted agencie (VM)
az vm identity assign \
--resource-group rg-devops \
--name vm-build-agent \
--role "Contributor" \
--scope /subscriptions/<sub-id>/resourceGroups/rg-production
# Dla GitHub Actions - użyj Workload Identity Federation (OIDC)
# Utwórz federated credential dla GitHub Actions (bez secretów!)
az ad app federated-credential create \
--id <app-object-id> \
--parameters '{
"name": "github-actions-main",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:my-org/my-repo:ref:refs/heads/main",
"audiences": ["api://AzureADTokenExchange"]
}'
# W GitHub Actions workflow - logowanie przez OIDC (nie podlega CA jak client_credentials)
# .github/workflows/deploy.yml:
# - uses: azure/login@v2
# with:
# client-id: $
# tenant-id: $
# subscription-id: $
Note: Managed Identity on Azure resources (App Service, VM, AKS) is NOT subject to CA policies in most scenarios. Workload Identity Federation (OIDC) is subject to CA policies for workload identities but does not require secrets - eliminating credential rotation and leakage concerns.
D) Configure Named Locations for CI/CD agent IP ranges:
If you need to maintain a CA policy on workload identities with a location condition - add agent IP ranges as trusted:
# Pobierz aktualne zakresy IP GitHub Actions (zmienne - sprawdzaj regularnie)
curl -s https://api.github.com/meta | jq '.actions[]' | head -20
# Pobierz zakresy IP Azure DevOps hosted agents
curl -s https://dev.azure.com/<org>/_apis/distributedtask/serviceconnectionproxies | jq '.value[].ipAddress'
# Zaktualizuj Named Location o nowe zakresy IP
LOCATION_ID="<named-location-id>"
az rest --method PATCH \
--url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/namedLocations/$LOCATION_ID" \
--headers "Content-Type=application/json" \
--body '{
"ipRanges": [
{"@odata.type": "#microsoft.graph.iPv4CidrRange", "cidrAddress": "4.148.0.0/14"},
{"@odata.type": "#microsoft.graph.iPv4CidrRange", "cidrAddress": "13.64.0.0/11"},
{"@odata.type": "#microsoft.graph.iPv4CidrRange", "cidrAddress": "20.36.0.0/14"},
{"@odata.type": "#microsoft.graph.iPv4CidrRange", "cidrAddress": "20.40.0.0/13"},
{"@odata.type": "#microsoft.graph.iPv4CidrRange", "cidrAddress": "40.74.0.0/15"}
]
}'
Note: GitHub Actions IP ranges change regularly. Automate Named Locations updates using an Azure Function or Logic App that queries
https://api.github.com/meta.
Validation
# 1. Test logowania service principal po zmianach
az login --service-principal \
-u <app-id> \
-p <secret> \
--tenant <tenant-id>
# Expected: logowanie pomyślne, brak błędu AADSTS53003
# 2. Sprawdź Sign-in Logs - brak nowych blokad CA
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/auditLogs/signIns?\$filter=appId eq '<app-id>' and createdDateTime ge $(date -u -d '-15 minutes' +%Y-%m-%dT%H:%M:%SZ)&\$top=5" \
--query "value[].{time:createdDateTime, status:status.errorCode, policy:appliedConditionalAccessPolicies[].{name:displayName,result:result}}" -o json
# Expected: status.errorCode = 0 (success), polityki z result = "notApplied" lub "success"
# 3. Test operacji end-to-end (np. Terraform plan)
terraform plan -out=tfplan
# Expected: plan generuje się bez błędów autoryzacji
# 4. Sprawdź politykę w Report-only mode (jeśli użyto opcji B)
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/auditLogs/signIns?\$filter=appId eq '<app-id>' and appliedConditionalAccessPolicies/any(p: p/displayName eq 'Workload Identity - Block untrusted locations' and p/result eq 'reportOnlyFailure')&\$top=5" \
--query "value[].{time:createdDateTime, ip:ipAddress}" -o table
# Expected: brak wpisów (logowania z CI/CD nie są blokowane w report-only)
# 5. Skonfiguruj alert na przyszłe blokady CA dla service principali
az monitor scheduled-query create \
--name "alert-ca-blocks-service-principals" \
--resource-group rg-monitoring \
--scopes "/subscriptions/<sub-id>/resourceGroups/rg-monitoring/providers/Microsoft.OperationalInsights/workspaces/law-security" \
--condition "count 'SigninLogs | where ResultType == 53003 and ServicePrincipalId != \"\" | where TimeGenerated > ago(15m)' > 0" \
--severity 2 \
--evaluation-frequency 5m \
--window-size 15m \
--action-groups "/subscriptions/<sub-id>/resourceGroups/rg-monitoring/providers/Microsoft.Insights/actionGroups/ag-platform-team" \
--description "Conditional Access blokuje service principal - wymagana interwencja"
Once you confirm that SP sign-in works and pipelines pass successfully, switch the policy from report-only mode to Enabled (if using solution B):
az rest --method PATCH \
--url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/<policy-id>" \
--headers "Content-Type=application/json" \
--body '{"state": "enabled"}'
Conditional Access blocking your automations?
Book a free 30-minute call. We will analyse your CA policies, configure workload identity exclusions, and implement a Zero Trust strategy that is safe for CI/CD.