Back to Checks
m365

At least one Conditional Access policy blocks legacy authentication

entra_legacy_authentication_blocked
Service: entraSeverity: critical
OfficialBuilt-inVerified
Categories:identity-accesse3
Resource Type:NotDefined

Microsoft Entra Conditional Access has an active policy that blocks legacy authentication for All users and All cloud apps by targeting legacy client app types (e.g., Exchange ActiveSync, other basic-auth clients) and enforcing Block access.

Prowler logoProwlerprowler-provider-m365

Risk

Allowing legacy authentication enables password spray and credential stuffing that bypass MFA, leading to account takeover. Compromised sessions threaten confidentiality (mail, files), integrity (settings, data changes), and availability, and enable lateral movement across Microsoft 365.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler m365 --checks entra_legacy_authentication_blocked

Recommendation

Enforce a tenant-wide policy to block legacy authentication for All users and All cloud apps, targeting legacy client app types. Migrate apps and devices to modern authentication. Keep minimal, monitored exclusions for break-glass/service accounts, prefer managed identities, and apply zero trust and least privilege.

Remediation

CLI

az rest --method post --url https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies --body '{"displayName":"<example_policy_name>","state":"enabled","conditions":{"users":{"includeUsers":["All"]},"applications":{"includeApplications":["All"]},"clientAppTypes":["exchangeActiveSync","other"]},"grantControls":{"builtInControls":["block"],"operator":"OR"}}'

Terraform

resource "azuread_conditional_access_policy" "<example_resource_name>" {
  display_name = "<example_resource_name>"
  state        = "enabled" # critical: enforce the policy

  conditions {
    users {
      include_users = ["All"] # critical: include all users
    }
    applications {
      include_applications = ["All"] # critical: include all cloud apps
    }
    client_app_types = ["exchangeActiveSync", "other"] # critical: target legacy auth clients
  }

  grant_controls {
    built_in_controls = ["block"] # critical: block access
  }
}

Other

1Go to Microsoft Entra admin center > Protection > Conditional Access > Policies
2Click New policy
3Users: Include > All users
4Target resources (cloud apps): Include > All apps
5Conditions > Client apps: Configure = Yes; select only Exchange ActiveSync clients and Other clients
6Grant > Block access > Select
7Enable policy: On, then Create

Check Code

from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.entra.entra_client import entra_client
from prowler.providers.m365.services.entra.entra_service import (
    ClientAppType,
    ConditionalAccessGrantControl,
    ConditionalAccessPolicyState,
)


class entra_legacy_authentication_blocked(Check):
    """Check if at least one Conditional Access policy blocks legacy authentication.

    This check ensures that at least one Conditional Access policy blocks legacy authentication.
    """

    def execute(self) -> list[CheckReportM365]:
        """Execute the check to ensure that at least one Conditional Access policy blocks legacy authentication.

        Returns:
            list[CheckReportM365]: A list containing the results of the check.
        """
        findings = []
        report = CheckReportM365(
            metadata=self.metadata(),
            resource={},
            resource_name="Conditional Access Policies",
            resource_id="conditionalAccessPolicies",
        )
        report.status = "FAIL"
        report.status_extended = (
            "No Conditional Access Policy blocks legacy authentication."
        )

        for policy in entra_client.conditional_access_policies.values():
            if policy.state == ConditionalAccessPolicyState.DISABLED:
                continue

            if "All" not in policy.conditions.user_conditions.included_users:
                continue

            if (
                "All"
                not in policy.conditions.application_conditions.included_applications
            ):
                continue

            if (
                ClientAppType.EXCHANGE_ACTIVE_SYNC
                not in policy.conditions.client_app_types
                or ClientAppType.OTHER_CLIENTS not in policy.conditions.client_app_types
            ):
                continue

            if (
                ConditionalAccessGrantControl.BLOCK
                in policy.grant_controls.built_in_controls
            ):
                report = CheckReportM365(
                    metadata=self.metadata(),
                    resource=policy,
                    resource_name=policy.display_name,
                    resource_id=policy.id,
                )
                if policy.state == ConditionalAccessPolicyState.ENABLED_FOR_REPORTING:
                    report.status = "FAIL"
                    report.status_extended = f"Conditional Access Policy '{policy.display_name}' reports legacy authentication but does not block it."
                else:
                    report.status = "PASS"
                    report.status_extended = f"Conditional Access Policy '{policy.display_name}' blocks legacy authentication."
                    break

        findings.append(report)
        return findings