Admin users have sign-in frequency enforced by Conditional Access at or below the recommended interval
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 Cloudprowler m365 --checks entra_admin_users_sign_in_frequency_enabledRecommendation
Use Conditional Access for admin roles to:
- Enforce short sign-in frequency (e.g.,
4hours, orEvery timefor 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
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