Back to Checks
m365

Admin users have sign-in frequency enforced by Conditional Access at or below the recommended interval

entra_admin_users_sign_in_frequency_enabled
Service: entraSeverity: high
OfficialBuilt-inVerified
Categories:identity-accesse3
Resource Type:NotDefined

Microsoft Entra Conditional Access evaluates whether admin roles are covered by policies that enforce a defined sign-in frequency and non-persistent browser sessions across all cloud apps. It looks for reauthentication set to a time interval or Every time, persistent browser set to never, and policies that are enforced rather than report-only or disabled.

Risk

Lax reauthentication and persistent sessions let admin tokens live too long, enabling session hijacking, token replay, and access after credential theft. Attackers can modify configurations, elevate privileges, and exfiltrate data, threatening confidentiality and integrity and increasing risk of tenant takeover.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler m365 --checks entra_admin_users_sign_in_frequency_enabled

Recommendation

Use Conditional Access for admin roles to:

  • Enforce short sign-in frequency (e.g., 4 hours, or Every time for critical actions)
  • Set persistent browser to never
  • Cover all apps and run in enforce mode

Pair with least privilege, MFA, PIM, and token protection to reduce session abuse.

Remediation

Terraform

resource "azuread_conditional_access_policy" "<example_resource_name>" {
  display_name = "<example_resource_name>"
  state        = "enabled" # Critical: must be enabled (not report-only) to enforce

  conditions {
    users {
      included_roles = ["<example_role_id>"] # Critical: target admin directory roles (e.g., Global Administrator)
    }
    applications {
      included_applications = ["All"] # Critical: apply to all cloud apps
    }
  }

  session_controls {
    sign_in_frequency          = 4          # Critical: enforce reauth at or below 4 hours
    sign_in_frequency_interval = "hours"   # Critical: time-based frequency in hours
    persistent_browser_mode    = "never"   # Critical: enforce non-persistent browser sessions
  }
}

Other

1Go to Microsoft Entra admin center (https://entra.microsoft.com/)
2Navigate to Protection > Conditional Access > Policies > New policy
3Users > Include > Select users and groups > Directory roles: select admin roles (e.g., Global Administrator)
4Target resources (Cloud apps): Select All cloud apps
5Session:
6- Enable Sign-in frequency and set to Every time OR set 4 hours (or less)
7- Set Persistent browser session to Never persistent
8Enable 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 (
    AdminRoles,
    ConditionalAccessPolicyState,
    SignInFrequencyInterval,
    SignInFrequencyType,
)


class entra_admin_users_sign_in_frequency_enabled(Check):
    """Check if Conditional Access policies enforce sign-in frequency for admin users."""

    def execute(self) -> list[CheckReportM365]:
        """Validate sign-in frequency enforcement for admin users."""
        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 enforces sign-in frequency for admin users."
        )
        recommended_frequency = entra_client.audit_config.get("sign_in_frequency", 4)

        for policy in entra_client.conditional_access_policies.values():
            if (
                policy.state == ConditionalAccessPolicyState.DISABLED
                or not {role.value for role in AdminRoles}.issuperset(
                    policy.conditions.user_conditions.included_roles
                )
                or "All"
                not in policy.conditions.application_conditions.included_applications
                or not policy.session_controls.sign_in_frequency.is_enabled
                or not policy.session_controls.persistent_browser.is_enabled
                or policy.session_controls.persistent_browser.mode != "never"
            ):
                continue

            report = CheckReportM365(
                metadata=self.metadata(),
                resource=policy,
                resource_name=policy.display_name,
                resource_id=policy.id,
            )

            if (
                policy.session_controls.sign_in_frequency.interval
                == SignInFrequencyInterval.EVERY_TIME
            ):
                if policy.state == ConditionalAccessPolicyState.ENABLED_FOR_REPORTING:
                    report.status = "FAIL"
                    report.status_extended = f"Conditional Access Policy '{policy.display_name}' only reports when sign-in frequency is 'Every Time' for admin users but does not enforce it."
                else:
                    report.status = "PASS"
                    report.status_extended = f"Conditional Access Policy '{policy.display_name}' enforces sign-in frequency 'Every Time' for admin users."
                    break
            elif (
                policy.session_controls.sign_in_frequency.interval
                == SignInFrequencyInterval.TIME_BASED
            ):
                frequency_hours = (
                    policy.session_controls.sign_in_frequency.frequency
                    if policy.session_controls.sign_in_frequency.type
                    == SignInFrequencyType.HOURS
                    else policy.session_controls.sign_in_frequency.frequency * 24
                )
                if frequency_hours > recommended_frequency:
                    report.status = "FAIL"
                    report.status_extended = f"Conditional Access Policy '{policy.display_name}' enforces sign-in frequency at {frequency_hours} hours for admin users, exceeding the recommended {recommended_frequency} hours."
                else:
                    if (
                        policy.state
                        == ConditionalAccessPolicyState.ENABLED_FOR_REPORTING
                    ):
                        report.status = "FAIL"
                        report.status_extended = f"Conditional Access Policy '{policy.display_name}' only reports when sign-in frequency is {frequency_hours} hours for admin users but does not enforce it."
                    else:
                        report.status = "PASS"
                        report.status_extended = f"Conditional Access Policy '{policy.display_name}' enforces sign-in frequency at {frequency_hours} hours for admin users."
                        break

        findings.append(report)
        return findings