Back to Checks
m365

Conditional Access policy enforces approved client apps or app protection for mobile devices

entra_conditional_access_policy_approved_client_app_required_for_mobile
Service: entraSeverity: medium
OfficialBuilt-inVerified
Categories:e3
Resource Type:NotDefined

Microsoft Entra Conditional Access policies can require that only approved client apps or apps with app protection policies are used on iOS and Android devices. This ensures corporate data on mobile platforms is accessed only through managed or protected applications.

Risk

Without requiring approved or protected client apps on mobile platforms, users can access corporate data through unmanaged applications that lack security controls. This increases the risk of data leakage, unauthorized data sharing, and exposure of sensitive information on personal or compromised mobile devices.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler m365 --checks entra_conditional_access_policy_approved_client_app_required_for_mobile

Recommendation

Enforce Conditional Access policies requiring app protection policies or approved client apps on iOS and Android devices. Prefer app protection policies over approved client apps, as the approved client app grant control is retiring June 30, 2026. Regularly review policies to ensure mobile access is restricted to managed applications.

Remediation

Other

1Navigate to the Microsoft Entra admin center https://entra.microsoft.com.
2Expand **Protection** > **Conditional Access** and select **Policies**.
3Select **New policy**.
4Under **Users**, include **All users**.
5Under **Target resources**, include **All cloud apps**.
6Under **Conditions** > **Device platforms**, select **Android** and **iOS**.
7Under **Grant**, select **Require app protection policy** (preferred) or **Require approved client app**.
8Set the operator to **OR**.
9Enable the policy and click **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 (
    ConditionalAccessGrantControl,
    ConditionalAccessPolicyState,
    GrantControlOperator,
)


class entra_conditional_access_policy_approved_client_app_required_for_mobile(Check):
    """Check if a Conditional Access policy requires approved client apps or app protection for mobile devices.

    This check ensures that at least one enabled Conditional Access policy
    targets iOS and Android platforms and requires approved client apps or
    app protection policies.
    - PASS: An enabled policy requires approved client apps or app protection for iOS/Android.
    - FAIL: No policy restricts mobile app access to approved or protected apps.
    """

    REQUIRED_MOBILE_PLATFORMS = {"android", "ios"}
    MOBILE_APP_GRANT_CONTROLS = {
        ConditionalAccessGrantControl.APPROVED_APPLICATION,
        ConditionalAccessGrantControl.COMPLIANT_APPLICATION,
    }

    def execute(self) -> list[CheckReportM365]:
        """Execute the check logic.

        Returns:
            A list of reports containing the result 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 requires approved client apps or app protection for mobile devices."

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

            if not policy.conditions.platform_conditions:
                continue

            included_platforms = set(
                policy.conditions.platform_conditions.include_platforms
            )
            excluded_platforms = set(
                policy.conditions.platform_conditions.exclude_platforms
            )

            targets_mobile_platforms = (
                "all" in included_platforms
                or self.REQUIRED_MOBILE_PLATFORMS.issubset(included_platforms)
            ) and not (
                "all" in excluded_platforms
                or self.REQUIRED_MOBILE_PLATFORMS.intersection(excluded_platforms)
            )
            if not targets_mobile_platforms:
                continue

            built_in_controls = set(policy.grant_controls.built_in_controls)
            has_mobile_app_control = bool(
                self.MOBILE_APP_GRANT_CONTROLS.intersection(built_in_controls)
            )
            if not has_mobile_app_control:
                continue

            if (
                policy.grant_controls.operator == GrantControlOperator.OR
                and not built_in_controls.issubset(self.MOBILE_APP_GRANT_CONTROLS)
            ):
                continue

            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 the requirement of approved client apps or app protection for mobile devices but does not enforce it."
            else:
                report.status = "PASS"
                report.status_extended = f"Conditional Access Policy {policy.display_name} requires approved client apps or app protection for mobile devices."
                break

        findings.append(report)

        return findings