Skip to content

Report on users without multi-factor authentication (MFA) registered.

Identifies users who have not enrolled in MFA — a critical security gap. Checks authentication methods (phone, authenticator app, FIDO2) to determine MFA readiness.

Inspired by ReportMFAStatusUsers.PS1 and ReportMFAStatusAdmins.PS1 from Office 365 for IT Pros.

Permissions

Required delegated permissions: User.Read.All Read user profiles UserAuthenticationMethod.Read.All Read authentication methods Directory.Read.All (optional) check admin roles

Reference

View source

from office365.graph_client import GraphClient
from tests import test_client_id, test_client_secret, test_tenant

STRONG_METHODS = {
    "microsoftAuthenticatorAuthenticationMethod",
    "phoneAuthenticationMethod",
    "fido2AuthenticationMethod",
    "windowsHelloForBusinessAuthenticationMethod",
    "passwordlessMicrosoftAuthenticatorAuthenticationMethod",
}

_DISPLAY_LIMIT = 20


def user_has_mfa(client: GraphClient, user_id: str) -> bool:
    """Check if a user has at least one strong MFA method registered."""
    try:
        methods = client.users[user_id].authentication.methods.get().execute_query()
        for method in methods:
            if method.type_name in STRONG_METHODS:
                return True
    except Exception:
        pass
    return False


def mfa_status_report() -> tuple[list[dict], list[dict]]:
    """Check MFA registration status for all users.

    Returns:
        Tuple of (users_with_mfa, users_without_mfa).
    """
    client = GraphClient(tenant=test_tenant).with_client_secret(test_client_id, test_client_secret)

    with_mfa = []
    without_mfa = []

    users = client.users.get().execute_query()
    for user in users:
        has = user_has_mfa(client, user.id)
        entry = {
            "upn": user.user_principal_name,
            "display_name": user.display_name,
            "user_type": getattr(user, "user_type", "Member"),
        }
        if has:
            with_mfa.append(entry)
        else:
            without_mfa.append(entry)

    return with_mfa, without_mfa


def main():
    print("Checking MFA registration status for all users...\n")
    with_mfa, without_mfa = mfa_status_report()

    total = len(with_mfa) + len(without_mfa)
    print(f"Total users: {total}")
    print(f"MFA registered: {len(with_mfa)} ({len(with_mfa) / total * 100:.0f}%)")
    print(f"No MFA:        {len(without_mfa)} ({len(without_mfa) / total * 100:.0f}%)")

    if without_mfa:
        print(f"\nUsers without MFA ({len(without_mfa)}):\n")
        for u in without_mfa[:_DISPLAY_LIMIT]:
            print(f"  {u['upn']:45s}  {u['display_name'][:30]}")
        if len(without_mfa) > _DISPLAY_LIMIT:
            print(f"\n  ... and {len(without_mfa) - _DISPLAY_LIMIT} more")


if __name__ == "__main__":
    main()

← Back to Users