Back to Checks
azure

Enabled user has signed in within the last 90 days

entra_user_with_recent_sign_in
Service: entraSeverity: medium
OfficialBuilt-inVerified
Categories:identity-access
Resource Type:NotDefined

Microsoft Entra ID enabled user accounts are evaluated for recent sign-in activity. Accounts that have not signed in for more than 90 days are flagged as stale. Stale accounts indicate orphaned identities that may have been abandoned after personnel changes, project completions, or role transitions without proper deprovisioning.

Risk

Stale accounts retain role assignments and group memberships. Attackers target dormant accounts via credential stuffing and password spraying because owners are unlikely to notice anomalous activity. Compromise enables lateral movement, data exfiltration, and persistence while evading detection tuned to active users.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler azure --checks entra_user_with_recent_sign_in

Recommendation

Implement automated access reviews in Entra ID to periodically review and remove stale accounts. Configure reviews to run quarterly, targeting all users or specific groups. Set auto-apply to disable accounts that are not confirmed by reviewers. For immediate remediation, filter users by last sign-in date and disable accounts inactive for more than 90 days after confirming with managers.

Remediation

CLI

az rest --method patch --url https://graph.microsoft.com/v1.0/users/<user_id> --body '{"accountEnabled":false}'

Other

1Sign in to Microsoft Entra admin center
2Go to Identity > Users > All users
3Add filter: Sign-in activity > Last interactive sign-in date is before <90 days ago>
4Review each stale account with the account owner or manager
5Disable accounts confirmed as no longer needed
6After a grace period, delete disabled accounts
7Establish a recurring access review to automate this process

Check Code

from datetime import datetime, timezone

from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.entra.entra_client import entra_client

STALE_THRESHOLD_DAYS = 90


class entra_user_with_recent_sign_in(Check):
    """
    Ensure enabled Entra ID users have signed in within the last 90 days.

    This check evaluates each enabled user's last interactive sign-in to detect stale or dormant accounts that should be reviewed or deprovisioned. Sign-in activity requires Entra ID P1/P2 licensing.

    - PASS: The enabled user signed in within the last 90 days.
    - FAIL: The enabled user has not signed in for more than 90 days, or has no recorded sign-in.
    - MANUAL (tenant-level): Microsoft Graph refused to return sign-in activity for the tenant (missing Entra ID P1/P2 licensing or the AuditLog.Read.All permission), or the tenant's users could not be retrieved at all, so the check cannot be evaluated; reported once per tenant.
    """

    def execute(self) -> list[Check_Report_Azure]:
        findings = []

        for tenant_domain, users in entra_client.users.items():
            if tenant_domain in entra_client.users_retrieval_errors:
                report = Check_Report_Azure(metadata=self.metadata(), resource={})
                report.subscription = f"Tenant: {tenant_domain}"
                report.resource_name = tenant_domain
                report.resource_id = entra_client.tenant_ids[0]
                report.status = "MANUAL"
                report.status_extended = (
                    f"Cannot evaluate sign-in activity for tenant {tenant_domain}: "
                    f"Microsoft Graph did not return the tenant's users "
                    f"({entra_client.users_retrieval_errors[tenant_domain]}). "
                    f"Retry the scan or review the tenant's users manually."
                )
                findings.append(report)
                continue

            if tenant_domain in entra_client.sign_in_activity_errors:
                report = Check_Report_Azure(metadata=self.metadata(), resource={})
                report.subscription = f"Tenant: {tenant_domain}"
                report.resource_name = tenant_domain
                report.resource_id = entra_client.tenant_ids[0]
                report.status = "MANUAL"
                report.status_extended = (
                    f"Cannot evaluate sign-in activity for tenant {tenant_domain}: "
                    f"Microsoft Graph did not return sign-in activity "
                    f"({entra_client.sign_in_activity_errors[tenant_domain]}). "
                    f"Verify that the tenant has Entra ID P1/P2 licensing and the "
                    f"scanning application has the AuditLog.Read.All permission."
                )
                findings.append(report)
                continue

            enabled_users = {k: v for k, v in users.items() if v.account_enabled}

            for user in enabled_users.values():
                report = Check_Report_Azure(metadata=self.metadata(), resource=user)
                report.subscription = f"Tenant: {tenant_domain}"

                if user.last_sign_in is None:
                    report.status = "FAIL"
                    report.status_extended = (
                        f"User {user.name} has no recorded sign-in activity."
                    )
                else:
                    last = user.last_sign_in
                    if last.tzinfo is None:
                        last = last.replace(tzinfo=timezone.utc)
                    days_since = (datetime.now(timezone.utc) - last).days
                    if days_since > STALE_THRESHOLD_DAYS:
                        report.status = "FAIL"
                        report.status_extended = (
                            f"User {user.name} has not signed in for {days_since} days."
                        )
                    else:
                        report.status = "PASS"
                        report.status_extended = (
                            f"User {user.name} signed in {days_since} days ago."
                        )

                findings.append(report)

        return findings