Skip to main content
emnode / learn
Compliance

Enable MFA for root and IAM users

One capability across the root user, IAM console and programmatic users, and Cognito user pools: make a stolen password or key useless on its own by requiring a second factor everywhere an identity can sign in.

14 min·10 sections·AWS

Last reviewed

Remediates AWS Security Hub: Cognito.5IAM.5IAM.6IAM.9IAM.19

Enabling MFA: the basics

What does "require a second factor" actually mean across AWS identities?

Multi-factor authentication (MFA) is one capability with several shapes. The root user has its own MFA, registered while signed in as root. Every IAM user can carry a virtual TOTP app, a FIDO2 hardware key, or a hardware token. Amazon Cognito user pools enforce MFA for your end customers through an MfaConfiguration setting. Each is a different control on the report, but the underlying idea is identical: a password or access key, on its own, should not be enough to act.

AWS Security Hub turns each of these into its own control. IAM.9 checks MFA on the root user, IAM.6 is the stricter hardware-only version of the same, IAM.5 checks MFA on IAM users with a console password, IAM.19 broadens that to every IAM user including programmatic-only ones, and Cognito.5 checks that a user pool requires MFA rather than leaving it optional. They look like separate findings, but they are one capability: every identity that can authenticate should need a second factor.

The reason this matters more than its mixed severities suggest is that credential compromise is still the most common entry route into a cloud account. A leaked root password is a total, unrecoverable takeover. A leaked programmatic key is exactly as dangerous as a phished admin password and far more likely to sit unnoticed in a committed file. Optional Cognito MFA leaves the majority of customer accounts protected by a reused password alone. MFA closes all three doors at once.

In this lesson you will learn how AWS expresses MFA across the root user, IAM console and programmatic users, and Cognito user pools, why the programmatic and customer-facing identities are often the most dangerous to leave unprotected, and how to enable a second factor everywhere without locking anyone out. The Controls this lesson covers section lists every Security Hub control in this capability, each linking to a deep page with the exact check and a copy-and-paste fix.

Fun fact

The keys nobody logs in with are the ones that leak

Annual secrets-sprawl research consistently finds millions of credentials accidentally committed to public code repositories every year, and AWS access keys are among the most common and the fastest weaponised. Automated scanners crawl new public commits continuously; a leaked key with broad permissions can be live-mining cryptocurrency before the developer who pushed it has finished their coffee. The identities IAM.19 catches that the console-only check misses, programmatic users with no console password, are exactly the ones whose keys end up in those commits, because nobody treats an automation account as a human-facing security risk. The same pattern shows up on the customer side: optional Cognito MFA leaves the majority of accounts protected by a password reused from some other site that has already been breached.

Finding identities without a second factor

Priya is the security lead at a scale-up preparing for its first SOC 2 audit. Security Hub shows MFA failures spread across the root user, a batch of IAM users, and a customer-facing Cognito pool in accounts that pre-date the team's current guardrails.

Rather than work the findings one by one, she starts by reading the account credential report, which lists every IAM user and whether MFA is active regardless of how they authenticate, so she can separate the high-risk identities from the rest before changing anything.

The credential report exposes mfa_active for every user, including programmatic-only ones the console-MFA check ignores. Filter to the users that fail IAM.19.

$ aws iam generate-credential-report && aws iam get-credential-report --query Content --output text | base64 -d | awk -F, 'NR==1 || $8=="false" {print $1","$4","$8","$10}'
user,password_enabled,mfa_active,access_key_1_active
ci-deployer,false,false,true
backup-runner,false,false,true
jess.intern,true,false,false
# Two programmatic-only users the console-MFA check never flags. IAM.19 catches them all.

Key-only automation users are the highest-risk MFA gap. Retire them for roles, or enrol them before flipping enforcement on.

How AWS evaluates MFAdeep dive

Root MFA (IAM.9) is read from the account summary flag AccountMFAEnabled via the Config rule root-account-mfa-enabled, which runs on a periodic schedule. It passes when any MFA device is enrolled, or when the root user has no sign-in credentials at all. IAM.6 is the stricter sibling: it passes only with a hardware device, so an account with a virtual authenticator on root passes IAM.9 but still fails IAM.6. Both must be done interactively while signed in as root; there is no IAM API that enrols MFA on root, by design.

The IAM user controls read the credential report. IAM.5 (mfa-enabled-for-iam-console-access) only evaluates users with a console password, while IAM.19 (iam-user-mfa-enabled) evaluates every IAM user with no further condition, which is why a programmatic-only user passes IAM.5 but fails IAM.19. AWS supports virtual TOTP, FIDO2 hardware keys, and hardware tokens; SMS MFA for IAM users was deprecated in 2019 because SIM-swap attacks make it materially weaker. FIDO2 is the strongest because the credential is bound to the sign-in domain and cannot be phished and replayed.

Cognito MFA (Cognito.5) is read from the user pool's MfaConfiguration. Only ON actually requires a second factor; OPTIONAL provides the illusion of MFA, since in practice only a small minority of users enrol voluntarily. Importantly, every one of these controls is a report of state, not an enforcement mechanism for IAM users: IAM.5 and IAM.19 tell you a user lacks a device, but enforcement comes from a conditional-deny policy keyed on aws:MultiFactorAuthPresent, or, more durably, from retiring the long-lived identity entirely.

What is the impact of leaving identities without MFA?

The direct impact is account takeover from a single leaked secret. For the root user the blast radius is unbounded and unrecoverable: no IAM policy can constrain root, so a password-only root user is one phished credential away from total control, uncapped fraudulent spend, and the ability to lock out every other administrator. For an IAM user the blast radius is whatever the user can do, and IAM users are disproportionately administrators or automation accounts with broad permissions.

The second-order impact is detection time. Key-only identities are rarely watched, so a breach through a leaked programmatic key often runs for days before anyone notices. On the customer side, credential-stuffing against a Cognito pool on OPTIONAL MFA is continuous and automated; a leaked password from any unrelated breach gets replayed against every login endpoint, and the majority of accounts that never enrolled have only that password protecting them.

On the compliance side, every modern framework, SOC 2, ISO 27001, PCI DSS and NIST 800-53, expects MFA on identities that can act on sensitive systems. A standing MFA failure during an audit is a clean, binary, evidence-backed negative finding that no process narrative can wave away, and it is hardest to argue away on exactly the identities that hold the broadest permissions.

How do you enable MFA everywhere safely?

Work the capability as one loop rather than chasing individual findings. The order matters: protect the irreplaceable root user first, then close the IAM gaps, then migrate customer-facing pools without locking users out.

1. Protect the root user first

Sign in as root and register an MFA device under Security Credentials. For production accounts prefer a hardware/FIDO key so you satisfy both IAM.9 and the stricter IAM.6 at once. Store the recovery codes separately, confirm the root email is a monitored distribution list, then stop using root for daily work. In AWS Organizations, consider centrally managing root so member accounts have no standing root credentials at all, which also makes IAM.9 pass by the no-credentials path.

2. Audit every IAM user, not just console ones

Generate the credential report and filter to mfa_active=false with no further condition, which is the IAM.19 set including the programmatic-only identities IAM.5 ignores. For each, record whether it has a console password, whether it has active access keys, and what permissions it holds. Human users get a device; service users get a decision about whether they should exist at all.

3. Enrol or retire, then enforce

Push FIDO2 hardware keys to humans with elevated permissions and give everyone a self-enrolment deadline. For programmatic users, the strongest fix is usually to retire the long-lived user for an IAM role the workload assumes, so the leakable secret disappears and the identity leaves the control's scope. Then attach a conditional-deny policy keyed on aws:MultiFactorAuthPresent so a user without MFA can enrol but otherwise cannot act.

4. Require MFA on Cognito pools

For customer-facing pools, run a short pre-enrolment campaign, then set MfaConfiguration to ON in a single set-user-pool-mfa-config call with TOTP primary and SMS as fallback. Enable Advanced Security Features for risk-based MFA so known-good devices skip the prompt. Back the whole capability with AWS Config rules and Service Control Policies so MFA cannot be quietly weakened later.

# Root MFA has no CLI equivalent: register it in the console while signed in as root
# (Security credentials > Multi-factor authentication > Assign MFA device).
# Then verify a device is bound to the root ARN and the summary flag flips to 1.
aws iam list-virtual-mfa-devices --assignment-status Assigned \
  --query 'VirtualMFADevices[?ends_with(SerialNumber, `:mfa/root-account-mfa-device`)].SerialNumber'
aws iam get-account-summary --query 'SummaryMap.AccountMFAEnabled'

# Enforce MFA on human IAM users with a conditional-deny policy keyed on the MFA flag.
aws iam attach-group-policy --group-name HumanUsers \
  --policy-arn arn:aws:iam::123456789012:policy/RequireMFAForUsers

# Require MFA pool-wide on a customer-facing Cognito pool.
aws cognito-idp set-user-pool-mfa-config --user-pool-id eu-west-1_aB3cD4eFg \
  --mfa-configuration ON --software-token-mfa-configuration Enabled=true

Quick quiz

Question 1 of 5

Security Hub shows MFA failures across the root user, a batch of IAM users, and a Cognito pool. What is the most efficient way to think about them?

You can now treat MFA as one capability rather than a scatter of findings: protect the irreplaceable root user first with a hardware device, close the IAM gaps including the programmatic-only identities the console check misses, require MFA on customer-facing Cognito pools, and back the whole capability with conditional-deny policies and Service Control Policies so it cannot be weakened. The Controls this lesson covers section below links every control in this group to its deep page and fix.

Back to the library

Controls this lesson covers

One capability, many AWS Security Hub controls. This lesson is the shared playbook; each control below keeps its own deep page with the exact check, severity and a copy-and-paste fix.