Skip to content

Authentication methods — audit registered methods per user and check

tenant-wide passwordless / MFA / SSPR readiness.

Shows three perspectives: 1. Tenant-wide summary: users registered by method and by feature. 2. Per-user detail: which methods each user has registered and what they're capable of (MFA, passwordless, SSPR). 3. Per-user authentication methods: FIDO2 keys, Microsoft Authenticator, phone, password.

Useful for MFA roll-out tracking, passwordless adoption reporting, and security compliance audits.

Permissions

Requires delegated permission UserAuthenticationMethod.Read.All and AuditLog.Read.All.

Reference

View source

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


def main():
    client = GraphClient(tenant=test_tenant).with_client_secret(test_client_id, test_client_secret)

    am = client.reports.authentication_methods

    # -- Step 1: tenant-wide summary by feature (MFA, SSPR, passwordless) --
    feature_summary_result = am.users_registered_by_feature().execute_query()
    fs = feature_summary_result.value
    print("Users by authentication feature:")
    print(f"  Total users         : {fs.totalUserCount}")
    print(f"  MFA capable      : {fs.mfaCapableCount or '?'}")
    print(f"  SSPR registered        : {fs.ssprRegisteredCount or '?'}")
    print(f"  SSPR enabled        : {fs.ssprEnabledCount or '?'}")
    print(f"  Passwordless capable: {fs.passwordlessCapableCount or '?'}")
    print()

    # -- Step 2: tenant-wide summary by method --
    method_summary = am.users_registered_by_method().execute_query()
    if method_summary and method_summary.value:
        ms = method_summary.value
        print("Users by authentication method:")
        if hasattr(ms, "users_registered_by_method") and ms.users_registered_by_method:
            for m in ms.users_registered_by_method:
                print(f"  {m.method_type or '?':30s}  {m.user_count or 0} users")
        elif hasattr(ms, "registration_count") and ms.registration_count:
            for rc in ms.registration_count:
                print(f"  {rc.method or '?':30s}  {rc.count or 0} users")
        print()

    # -- Step 3: per-user registration details --
    details = am.user_registration_details.top(20).get().execute_query()
    print(f"User registration details (showing {len(details)} users):\n")
    print(f"{'UPN':35s} {'MFA':6s} {'PW-less':9s} {'SSPR-cap':9s} {'SSPR-en':8s} {'Admin':6s}  {'Preferred method'}")
    print("-" * 100)

    for d in details:
        pref = d.user_preferred_method_for_secondary_authentication or "-"
        print(
            f"{d.user_principal_name[:33]:35s} "
            f"{'✔' if d.is_mfa_registered else '✗':6s} "
            f"{'✔' if d.is_passwordless_capable else '✗':9s} "
            f"{'✔' if d.is_sspr_capable else '✗':9s} "
            f"{'✔' if d.is_sspr_enabled else '✗':8s} "
            f"{'✔' if d.is_admin else '':6s}  "
            f"{pref}"
        )

    # -- Step 4: per-user registered methods for a specific user --
    print()
    sample_upn = details[0].user_principal_name if details else "admin@contoso.onmicrosoft.com"
    users = client.users.filter(f"userPrincipalName eq '{sample_upn}'").get().execute_query()
    if users:
        user = users[0]
        auth = user.authentication
        all_methods = auth.methods.get().execute_query()
        print(f"Authentication methods for {user.user_principal_name}: {len(all_methods)}")
        for m in all_methods:
            mtype = m.properties.get("@odata.type", m.entity_type_name)
            extra = ""
            if "phone" in mtype.lower():
                extra = m.properties.get("phoneNumber", "") or m.properties.get("phoneType", "")
            elif "fido" in mtype.lower():
                extra = m.properties.get("displayName", "")
            elif "microsoftAuthenticator" in mtype.lower():
                extra = m.properties.get("displayName", "")
            print(f"  {mtype:55s}  {extra}")


if __name__ == "__main__":
    main()

← Back to Users