{"id":71705,"date":"2024-06-20T10:27:31","date_gmt":"2024-06-20T10:27:31","guid":{"rendered":"https:\/\/logmeonce.com\/resources\/2023\/08\/13\/aws-cli-multi-factor-authentication\/"},"modified":"2026-10-21T00:00:20","modified_gmt":"2026-10-21T00:00:20","slug":"aws-cli-multi-factor-authentication","status":"publish","type":"post","link":"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/","title":{"rendered":"AWS CLI Multi-Factor Authentication: Setup and Enforcement"},"content":{"rendered":"<div class=\"336cb5b64765e27a1a6c1bb71b941f1a\" data-index=\"1\" style=\"float: none; margin:10px 0 10px 0; text-align:center;\">\n<script async src=\"https:\/\/pagead2.googlesyndication.com\/pagead\/js\/adsbygoogle.js?client=ca-pub-4830628043307652\"\r\n     crossorigin=\"anonymous\"><\/script>\r\n<!-- above content -->\r\n<ins class=\"adsbygoogle\"\r\n     style=\"display:block\"\r\n     data-ad-client=\"ca-pub-4830628043307652\"\r\n     data-ad-slot=\"5864845439\"\r\n     data-ad-format=\"auto\"\r\n     data-full-width-responsive=\"true\"><\/ins>\r\n<script>\r\n     (adsbygoogle = window.adsbygoogle || []).push({});\r\n<\/script>\n<\/div>\n<\/p>\n<p>To use multi-factor authentication with the AWS CLI, you exchange your long-lived IAM user access key plus a current MFA code for temporary credentials, then run your commands with those. The newer <code>aws configure mfa-login<\/code> command does the exchange for you. If you can, skip the manual route and sign in through IAM Identity Center, which handles MFA in the browser, including passkeys and security keys. This guide covers both paths, the policy that makes MFA mandatory, and the mistakes that quietly turn enforcement off.<\/p>\n<div data-blg-cta=\"after_tldr\" data-blg-cta-layout=\"banner\" style=\"margin:28px 0;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif\">\n<div style=\"border-radius:26px;padding:min(22px,3.2vw)\">\n<div style=\"background:#ffffff;border-radius:18px;overflow:hidden\">\n<div style=\"padding:34px 30px;text-align:center\">\n<div style=\"margin:0 0 18px\"><span style=\"max-width:100%;border-radius:999px;padding:6px 13px;font-size:12px;font-weight:800;letter-spacing:0.1em;text-transform:uppercase;line-height:1.3;background:#F47F24;color:#ffffff\">Logmeonce<\/span><\/div>\n<div style=\"font-size:26px;font-weight:800;line-height:1.2;letter-spacing:-0.01em;color:#1f2937;margin:0\">Protect Credentials Beyond the CLI<\/div>\n<div style=\"width:56px;height:6px;border-radius:3px;background:#F47F24;margin:12px 0 14px;margin-left:auto;margin-right:auto\"><\/div>\n<div style=\"font-size:15px;line-height:1.55;color:#64748b;margin:0 0 24px;max-width:44em;margin-left:auto;margin-right:auto\">Explore LogMeOnce\u2019s password management and identity security resources to help protect the credentials behind your cloud access.<\/div>\n<p><a href=\"https:\/\/logmeonce.com\/resources\" style=\"align-items:center;gap:9px;border-radius:10px;font-weight:700;font-size:15px;text-decoration:none;padding:13px 22px 13px 26px;background:#F47F24;color:#ffffff\">Explore security resources<\/a><\/div>\n<\/div>\n<\/div>\n<\/div>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_77 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#TLDR\" >TL;DR<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Why_the_CLI_needs_its_own_MFA_step\" >Why the CLI needs its own MFA step<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Path_1_MFA_with_an_IAM_user\" >Path 1: MFA with an IAM user<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Step_1_Assign_an_MFA_device\" >Step 1: Assign an MFA device<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Step_2_Set_up_a_base_profile\" >Step 2: Set up a base profile<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Step_3_Get_temporary_credentials\" >Step 3: Get temporary credentials<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Step_4_Know_how_long_the_session_lasts\" >Step 4: Know how long the session lasts<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Step_5_Use_the_temporary_profile\" >Step 5: Use the temporary profile<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Path_2_IAM_Identity_Center_and_aws_sso_login\" >Path 2: IAM Identity Center and aws sso login<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Enforcing_MFA_instead_of_hoping_for_it\" >Enforcing MFA instead of hoping for it<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Use_the_right_condition_operator\" >Use the right condition operator<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Require_a_fresh_token_for_sensitive_resources\" >Require a fresh token for sensitive resources<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#GetSessionToken_versus_AssumeRole\" >GetSessionToken versus AssumeRole<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Common_errors_and_what_they_mean\" >Common errors and what they mean<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Our_perspective_protect_the_key_not_just_the_code\" >Our perspective: protect the key, not just the code<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Where_a_password_manager_fits_in_an_AWS_workflow\" >Where a password manager fits in an AWS workflow<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#A_rollout_checklist_for_admins\" >A rollout checklist for admins<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#FAQ\" >FAQ<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-19\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#How_do_I_use_MFA_with_the_AWS_CLI\" >How do I use MFA with the AWS CLI?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-20\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#How_long_do_the_temporary_credentials_last\" >How long do the temporary credentials last?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-21\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Can_I_use_a_YubiKey_or_passkey_with_the_AWS_CLI\" >Can I use a YubiKey or passkey with the AWS CLI?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-22\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Why_does_my_MFA_policy_not_block_anything\" >Why does my MFA policy not block anything?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-23\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Is_MFA_enough_to_secure_IAM_user_access_keys\" >Is MFA enough to secure IAM user access keys?<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-24\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Sources\" >Sources<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-25\" href=\"https:\/\/logmeonce.com\/resources\/aws-cli-multi-factor-authentication\/#Recommended\" >Recommended<\/a><\/li><\/ul><\/nav><\/div>\n<h2 id=\"tldr\"><span class=\"ez-toc-section\" id=\"TLDR\"><\/span>TL;DR<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<ul>\n<li>IAM users get MFA in the CLI by trading an access key and an MFA code for temporary credentials. <a href=\"https:\/\/docs.aws.amazon.com\/cli\/latest\/reference\/configure\/mfa-login.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><code>aws configure mfa-login<\/code><\/a> automates this and writes the result to a profile.<\/li>\n<li>Session credentials last 12 hours by default and can be set from 15 minutes to 36 hours, <a href=\"https:\/\/repost.aws\/knowledge-center\/authenticate-mfa-cli\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">according to AWS re:Post<\/a>.<\/li>\n<li>Hardware security keys and passkeys do not work with the manual token flow. Use IAM Identity Center and <code>aws sso login<\/code> for those.<\/li>\n<li>To force MFA, deny actions with <code>BoolIfExists<\/code> on <code>aws:MultiFactorAuthPresent<\/code>. A plain <code>Bool<\/code> check fails silently.<\/li>\n<li>Whatever route you take, store the underlying secrets in a proper password manager, not in plain text files.<\/li>\n<\/ul>\n<h2 id=\"why-the-cli-needs-its-own-mfa-step\"><span class=\"ez-toc-section\" id=\"Why_the_CLI_needs_its_own_MFA_step\"><\/span>Why the CLI needs its own MFA step<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Signing in to the AWS console with MFA protects the console. It does nothing for an access key sitting in <code>~\/.aws\/credentials<\/code>. Anyone who copies that file can call the API with no second factor at all.<\/p>\n<p>The fix is to make the long-lived key useless on its own. You require that API calls come from a session that was created with an MFA code. The key becomes a way to ask for a session, not a way to do work.<\/p>\n<p>There are two models, and which one you use decides everything else in this guide:<\/p>\n<table>\n<thead>\n<tr>\n<th><\/th>\n<th>IAM user + MFA token<\/th>\n<th>IAM Identity Center<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Credentials on disk<\/td>\n<td>Long-lived access key plus temporary session<\/td>\n<td>Short-lived, cached after browser login<\/td>\n<\/tr>\n<tr>\n<td>MFA entry<\/td>\n<td>Type a code on each session refresh<\/td>\n<td>Browser prompt at the portal<\/td>\n<\/tr>\n<tr>\n<td>Security keys and passkeys<\/td>\n<td>Not supported in the CLI flow<\/td>\n<td>Supported through the browser<\/td>\n<\/tr>\n<tr>\n<td>Best for<\/td>\n<td>Legacy accounts, break-glass users, small setups<\/td>\n<td>Teams, MSPs, anything with more than a handful of people<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>AWS itself <a href=\"https:\/\/docs.aws.amazon.com\/IAM\/latest\/UserGuide\/best-practices.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">recommends using temporary credentials and federation<\/a> for human users rather than IAM users with access keys. The IAM user path still matters because plenty of accounts have it, and because you need to understand it to enforce MFA correctly.<\/p>\n<h2 id=\"path-1-mfa-with-an-iam-user\"><span class=\"ez-toc-section\" id=\"Path_1_MFA_with_an_IAM_user\"><\/span>Path 1: MFA with an IAM user<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<h3 id=\"step-1-assign-an-mfa-device\"><span class=\"ez-toc-section\" id=\"Step_1_Assign_an_MFA_device\"><\/span>Step 1: Assign an MFA device<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In the console, open the IAM user, go to the Security credentials tab and choose Manage next to the assigned MFA device. A virtual authenticator app is the usual choice. Note the device ARN, which looks like <code>arn:aws:iam::123456789012:mfa\/your-user<\/code>. You need it for the CLI.<\/p>\n<h3 id=\"step-2-set-up-a-base-profile\"><span class=\"ez-toc-section\" id=\"Step_2_Set_up_a_base_profile\"><\/span>Step 2: Set up a base profile<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Run <code>aws configure<\/code> with the user\u2019s access key and secret key. This is your long-lived profile. Treat it as the dangerous one: by itself it should be able to do almost nothing once the enforcement policy below is in place.<\/p>\n<h3 id=\"step-3-get-temporary-credentials\"><span class=\"ez-toc-section\" id=\"Step_3_Get_temporary_credentials\"><\/span>Step 3: Get temporary credentials<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The newer route is the CLI\u2019s own helper. <a href=\"https:\/\/docs.aws.amazon.com\/cli\/latest\/reference\/configure\/mfa-login.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">The <code>mfa-login<\/code> command<\/a> gets temporary security credentials and places them in an AWS profile. It uses the long-lived access key and the MFA code from a virtual TOTP device. A typical call looks like this:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/media.babylovegrowth.ai\/blog-images\/organization-6456\/1792418588689_Flow-from-MFA-code-to-temporary-credentials.jpeg\" alt=\"Flow from MFA code to temporary credentials\" title=\"\"><\/p>\n<pre><code>aws configure mfa-login --profile myprofile --update-profile mfaprofile\n<\/code><\/pre>\n<p>The older, manual route calls the Security Token Service directly:<\/p>\n<pre><code>aws sts get-session-token \n  --serial-number arn:aws:iam::123456789012:mfa\/your-user \n  --token-code 123456\n<\/code><\/pre>\n<p>The response contains an access key ID, a secret access key and a session token. Put all three into a second profile, or export them as environment variables, and use that profile for real work. The steps are laid out in the <a href=\"https:\/\/repost.aws\/knowledge-center\/authenticate-mfa-cli\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">AWS re:Post guide to MFA on the CLI<\/a>.<\/p>\n<h3 id=\"step-4-know-how-long-the-session-lasts\"><span class=\"ez-toc-section\" id=\"Step_4_Know_how_long_the_session_lasts\"><\/span>Step 4: Know how long the session lasts<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>These credentials are valid for 12 hours by default. You can request anything from 15 minutes to 36 hours, per the same re:Post article. Shorter is safer for production access. Longer is tempting for scripts, but a script that needs 36 hours of unattended access probably should not be using a human\u2019s MFA at all. Give it a role instead.<\/p>\n<h3 id=\"step-5-use-the-temporary-profile\"><span class=\"ez-toc-section\" id=\"Step_5_Use_the_temporary_profile\"><\/span>Step 5: Use the temporary profile<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<pre><code>aws s3 ls --profile mfaprofile\n<\/code><\/pre>\n<p>When the session expires, calls start failing with an access denied or expired-token error. Run the login again. This is the friction people complain about, and it is why many teams eventually move to Identity Center.<\/p>\n<h2 id=\"path-2-iam-identity-center-and-aws-sso-login\"><span class=\"ez-toc-section\" id=\"Path_2_IAM_Identity_Center_and_aws_sso_login\"><\/span>Path 2: IAM Identity Center and <code>aws sso login<\/code><span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>IAM Identity Center (formerly AWS SSO) moves authentication out of the terminal. You configure a profile once with <code>aws configure sso<\/code>, then run <code>aws sso login<\/code>. The CLI opens your browser, you sign in and pass MFA there, and <a href=\"https:\/\/docs.aws.amazon.com\/cli\/latest\/reference\/sso\/login.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">the CLI caches short-lived credentials<\/a>.<\/p>\n<p>This matters for three reasons.<\/p>\n<ol>\n<li><strong>Security keys and passkeys work.<\/strong> The older GetSessionToken flow does not support FIDO\/U2F\/WebAuthn devices. AWS re:Post says <a href=\"https:\/\/repost.aws\/knowledge-center\/authenticate-mfa-cli\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">users with those devices must use IAM Identity Center<\/a>, which uses browser-based MFA.<\/li>\n<li><strong>No long-lived key on the laptop.<\/strong> There is no access key to steal from a dotfile.<\/li>\n<li><strong>Refresh is automatic.<\/strong> With the <a href=\"https:\/\/docs.aws.amazon.com\/cli\/latest\/userguide\/sso-configure-profile-token.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">SSO token provider configuration<\/a>, the CLI requests new credentials when the old ones expire, as long as your Identity Center session is still active. You only repeat the browser login when the portal session itself ends.<\/li>\n<\/ol>\n<p>The basic flow:<\/p>\n<pre><code>aws configure sso\naws sso login --profile my-sso-profile\naws s3 ls --profile my-sso-profile\n<\/code><\/pre>\n<p>For a managed service provider handling dozens of client accounts, this is the practical choice. One login, many account profiles, MFA defined centrally. The <a href=\"https:\/\/docs.aws.amazon.com\/cli\/latest\/userguide\/cli-configure-sso.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">AWS CLI documentation on configuring Identity Center<\/a> walks through setup in detail.<\/p>\n<h2 id=\"enforcing-mfa-instead-of-hoping-for-it\"><span class=\"ez-toc-section\" id=\"Enforcing_MFA_instead_of_hoping_for_it\"><\/span>Enforcing MFA instead of hoping for it<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Setting up MFA does not require anyone to use it. If the base access key can still call <code>ec2:TerminateInstances<\/code>, a stolen key skips the whole exercise. Enforcement lives in IAM policy.<\/p>\n<h3 id=\"use-the-right-condition-operator\"><span class=\"ez-toc-section\" id=\"Use_the_right_condition_operator\"><\/span>Use the right condition operator<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The key to know is <code>aws:MultiFactorAuthPresent<\/code>. The trap is how it behaves in sessions without MFA: the key is simply absent. AWS\u2019s own example for <a href=\"https:\/\/docs.aws.amazon.com\/IAM\/latest\/UserGuide\/reference_policies_examples_ec2_require-mfa.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">requiring MFA on specific EC2 operations<\/a> therefore uses <code>BoolIfExists<\/code> rather than a plain <code>Bool<\/code> check. With <code>Bool<\/code> set to false, a request with no MFA key never matches, so the deny never fires and access goes through.<\/p>\n<p>A minimal deny statement:<\/p>\n<pre><code>{\n  &quot;Effect&quot;: &quot;Deny&quot;,\n  &quot;Action&quot;: [&quot;ec2:StopInstances&quot;, &quot;ec2:TerminateInstances&quot;],\n  &quot;Resource&quot;: &quot;*&quot;,\n  &quot;Condition&quot;: {\n    &quot;BoolIfExists&quot;: {&quot;aws:MultiFactorAuthPresent&quot;: &quot;false&quot;}\n  }\n}\n<\/code><\/pre>\n<p>Read it as: deny these actions whenever MFA is absent or false. Test it with the base profile and confirm you get an explicit deny, then test with the MFA profile.<\/p>\n<h3 id=\"require-a-fresh-token-for-sensitive-resources\"><span class=\"ez-toc-section\" id=\"Require_a_fresh_token_for_sensitive_resources\"><\/span>Require a fresh token for sensitive resources<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>MFA present is not the same as MFA recent. If you want a recent token for a production bucket, add <code>aws:MultiFactorAuthAge<\/code>. AWS\u2019s <a href=\"https:\/\/docs.aws.amazon.com\/IAM\/latest\/UserGuide\/reference_policies_examples_s3_full-access-except-production.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">S3 example denying production access without recent MFA<\/a> uses a limit of 1800 seconds, or 30 minutes. Pair it with <code>NumericGreaterThanIfExists<\/code> so a missing age also triggers the deny.<\/p>\n<p>The practical effect: someone who logged in with MFA at 9 a.m. cannot delete production objects at 3 p.m. without authenticating again.<\/p>\n<h3 id=\"getsessiontoken-versus-assumerole\"><span class=\"ez-toc-section\" id=\"GetSessionToken_versus_AssumeRole\"><\/span>GetSessionToken versus AssumeRole<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The two calls treat MFA differently, and mixing them up is a common source of confusion. Credentials from GetSessionToken carry the MFA information, so resource-level policies can check it on every call. Credentials from AssumeRole do not carry that context the same way. AWS\u2019s page on <a href=\"https:\/\/docs.aws.amazon.com\/IAM\/latest\/UserGuide\/id_credentials_mfa_configure-api-require.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">securing API access with MFA<\/a> explains the difference. For roles, you enforce MFA in the role\u2019s trust policy, at the moment of assumption.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/media.babylovegrowth.ai\/blog-images\/organization-6456\/1792418554914_GetSessionToken-versus-AssumeRole-overview-diagram.jpeg\" alt=\"GetSessionToken versus AssumeRole \u2014 overview diagram\" title=\"\"><\/p>\n<p>In a cross-account setup that means the trust policy of the target role should require MFA, and the source account must be tidy too. If the source account does not restrict who can manage MFA devices, an attacker with enough permissions could register a device and produce an MFA claim that satisfies the target\u2019s trust policy. Lock down <code>iam:CreateVirtualMFADevice<\/code>, <code>iam:EnableMFADevice<\/code> and friends for everyone but administrators.<\/p>\n<h2 id=\"common-errors-and-what-they-mean\"><span class=\"ez-toc-section\" id=\"Common_errors_and_what_they_mean\"><\/span>Common errors and what they mean<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<ul>\n<li><strong>\u201cAccessDenied\u201d right after configuring MFA.<\/strong> You are probably still using the base profile. Check <code>aws sts get-caller-identity --profile mfaprofile<\/code> and confirm the session profile is the one in use.<\/li>\n<li><strong>\u201cMultiFactorAuthentication failed with invalid MFA one time pass code.\u201d<\/strong> Usually clock drift on the device, a code that rolled over mid-command, or the wrong device ARN. Wait for the next code and retry.<\/li>\n<li><strong>Commands worked this morning, fail now.<\/strong> The session expired. Re-run the login.<\/li>\n<li><strong>Policy seems to do nothing.<\/strong> Check you used <code>BoolIfExists<\/code>, not <code>Bool<\/code>, as above.<\/li>\n<li><strong>Security key will not work in the terminal.<\/strong> Expected. Switch to Identity Center.<\/li>\n<li><strong>A script breaks after you enforce MFA.<\/strong> Scripts should assume a role through a proper non-human mechanism instead of reusing a person\u2019s code.<\/li>\n<\/ul>\n<h2 id=\"our-perspective-protect-the-key-not-just-the-code\"><span class=\"ez-toc-section\" id=\"Our_perspective_protect_the_key_not_just_the_code\"><\/span>Our perspective: protect the key, not just the code<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We build password and identity security tools, so we tend to look at this from the credential\u2019s point of view. MFA on the CLI is a good control, but it protects the session. It does not protect the access key, the TOTP seed, or the recovery codes sitting in a notes file.<\/p>\n<p>Three habits make the biggest difference in the teams we talk to:<\/p>\n<ol>\n<li><strong>Keep the secret out of the shell history and out of dotfiles.<\/strong> The base access key and the MFA device ARN belong in an encrypted vault with access controls, not pasted into a wiki page.<\/li>\n<li><strong>Prefer phishing-resistant factors.<\/strong> A six-digit code can be typed into a fake page. A passkey or security key tied to the real domain cannot. That is the logic behind <a href=\"https:\/\/logmeonce.com\/passwordless-mfa\" target=\"_blank\" rel=\"noopener\">passwordless MFA<\/a>, and it is why Identity Center\u2019s browser flow is a step up from typing codes into a terminal.<\/li>\n<li><strong>Reduce how many humans hold long-lived keys.<\/strong> Every IAM user with an access key is a standing target. Fewer keys means fewer things to rotate, audit and lose. For organizations, this is the same lesson as the <a href=\"https:\/\/logmeonce.com\/blog\/password-management\/enterprise-password-management-mistakes-you-dont-want-to-make\/\" target=\"_blank\" rel=\"noopener\">common enterprise password management mistakes<\/a>: sprawl, shared secrets and missing offboarding do the damage, not exotic attacks.<\/li>\n<\/ol>\n<p>AWS makes the same case in its documentation, steering teams toward temporary credentials and federation. Use MFA on the CLI where you must, and shrink the number of places you must.<\/p>\n<blockquote>\n<p><em>\u2014 Mike<\/em><\/p>\n<\/blockquote>\n<h2 id=\"where-a-password-manager-fits-in-an-aws-workflow\"><span class=\"ez-toc-section\" id=\"Where_a_password_manager_fits_in_an_AWS_workflow\"><\/span>Where a password manager fits in an AWS workflow<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Even with Identity Center, people still hold the console password for the root user, the credentials for the identity provider, the recovery codes for MFA devices and API keys for other services. Those need a home that is encrypted, shared on a need-to-know basis and recoverable when someone leaves.<\/p>\n<p>LogMeOnce offers a password manager for individuals and teams. Personal plans: <a href=\"https:\/\/logmeonce.com\/password-manager\/\" target=\"_blank\" rel=\"noopener\">Professional is $2.50 per month, Ultimate is $3.25 per month and Family is $4.99 per month<\/a>. For organizations, <a href=\"https:\/\/logmeonce.com\/business-pricing-and-comparison\/\" target=\"_blank\" rel=\"noopener\">Teams is $4.00 per month per user and Business is $7.99 per month per user<\/a>, with an Enterprise tier that has no published price. Managed service providers can see the <a href=\"https:\/\/logmeonce.com\/resources\/\" target=\"_blank\" rel=\"noopener\">full range of products on our resources page<\/a>. If you are weighing the return for a team, the <a href=\"https:\/\/logmeonce.com\/password-manager-roi-calculator\" target=\"_blank\" rel=\"noopener\">password manager ROI calculator<\/a> gives you a starting figure.<\/p>\n<h2 id=\"a-rollout-checklist-for-admins\"><span class=\"ez-toc-section\" id=\"A_rollout_checklist_for_admins\"><\/span>A rollout checklist for admins<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<ol>\n<li>Inventory IAM users with access keys. Delete the ones nobody uses.<\/li>\n<li>For humans, set up IAM Identity Center and move CLI access to <code>aws sso login<\/code>.<\/li>\n<li>For remaining IAM users, assign MFA devices and document the <code>mfa-login<\/code> or <code>get-session-token<\/code> steps.<\/li>\n<li>Attach a deny policy with <code>BoolIfExists<\/code> so the base key cannot do real work without MFA.<\/li>\n<li>Add <code>aws:MultiFactorAuthAge<\/code> limits to production resources.<\/li>\n<li>Require MFA in the trust policy of any role that people assume, and restrict MFA device management in the source accounts.<\/li>\n<li>Move workloads and automation to roles with temporary credentials, not to someone\u2019s personal key.<\/li>\n<li>Store recovery codes and root credentials in an encrypted vault with audited access.<\/li>\n<li>Test with the base profile, then the MFA profile, and record the expected deny.<\/li>\n<\/ol>\n<h2 id=\"faq\"><span class=\"ez-toc-section\" id=\"FAQ\"><\/span>FAQ<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<h3 id=\"how-do-i-use-mfa-with-the-aws-cli\"><span class=\"ez-toc-section\" id=\"How_do_I_use_MFA_with_the_AWS_CLI\"><\/span>How do I use MFA with the AWS CLI?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Assign an MFA device to your IAM user, then run <code>aws configure mfa-login<\/code> or <code>aws sts get-session-token<\/code> with your device ARN and a current code. Use the temporary credentials it returns in a separate profile. If your organization uses IAM Identity Center, run <code>aws sso login<\/code> instead and complete MFA in the browser.<\/p>\n<h3 id=\"how-long-do-the-temporary-credentials-last\"><span class=\"ez-toc-section\" id=\"How_long_do_the_temporary_credentials_last\"><\/span>How long do the temporary credentials last?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The default is 12 hours. You can request anything between 15 minutes and 36 hours, according to <a href=\"https:\/\/repost.aws\/knowledge-center\/authenticate-mfa-cli\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">AWS re:Post<\/a>. Choose shorter lifetimes for production or administrative access.<\/p>\n<h3 id=\"can-i-use-a-yubikey-or-passkey-with-the-aws-cli\"><span class=\"ez-toc-section\" id=\"Can_I_use_a_YubiKey_or_passkey_with_the_AWS_CLI\"><\/span>Can I use a YubiKey or passkey with the AWS CLI?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Not through the GetSessionToken flow, which takes a typed code. Security keys and passkeys work through IAM Identity Center, where the browser handles the MFA step during <code>aws sso login<\/code>.<\/p>\n<h3 id=\"why-does-my-mfa-policy-not-block-anything\"><span class=\"ez-toc-section\" id=\"Why_does_my_MFA_policy_not_block_anything\"><\/span>Why does my MFA policy not block anything?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Most often the condition uses <code>Bool<\/code> with <code>aws:MultiFactorAuthPresent<\/code> set to false. In sessions without MFA that key is missing, so the condition never matches. Switch to <code>BoolIfExists<\/code>, as in <a href=\"https:\/\/docs.aws.amazon.com\/IAM\/latest\/UserGuide\/reference_policies_examples_ec2_require-mfa.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">AWS\u2019s EC2 example<\/a>.<\/p>\n<h3 id=\"is-mfa-enough-to-secure-iam-user-access-keys\"><span class=\"ez-toc-section\" id=\"Is_MFA_enough_to_secure_IAM_user_access_keys\"><\/span>Is MFA enough to secure IAM user access keys?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>No. MFA limits what a stolen key can do, but AWS still <a href=\"https:\/\/docs.aws.amazon.com\/cli\/latest\/userguide\/cli-authentication-user.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">advises against long-lived IAM user keys<\/a> for real data and recommends federation such as IAM Identity Center. Treat MFA on IAM users as a fallback, not the goal.<\/p>\n<h2 id=\"sources\"><span class=\"ez-toc-section\" id=\"Sources\"><\/span>Sources<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<ul>\n<li><a href=\"https:\/\/repost.aws\/knowledge-center\/authenticate-mfa-cli\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">Use the AWS CLI with MFA to authenticate | AWS re:Post<\/a><\/li>\n<li><a href=\"https:\/\/docs.aws.amazon.com\/cli\/latest\/reference\/configure\/mfa-login.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">mfa-login, AWS CLI Command Reference<\/a><\/li>\n<li><a href=\"https:\/\/docs.aws.amazon.com\/IAM\/latest\/UserGuide\/best-practices.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">Security best practices in IAM<\/a><\/li>\n<li><a href=\"https:\/\/docs.aws.amazon.com\/cli\/latest\/reference\/sso\/login.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">login, AWS CLI Command Reference<\/a><\/li>\n<li><a href=\"https:\/\/docs.aws.amazon.com\/cli\/latest\/userguide\/sso-configure-profile-token.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">Configuring IAM Identity Center authentication with the AWS CLI (token provider)<\/a><\/li>\n<li><a href=\"https:\/\/docs.aws.amazon.com\/cli\/latest\/userguide\/cli-configure-sso.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">Configuring IAM Identity Center authentication with the AWS CLI<\/a><\/li>\n<li><a href=\"https:\/\/docs.aws.amazon.com\/IAM\/latest\/UserGuide\/reference_policies_examples_ec2_require-mfa.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">Amazon EC2: Requires MFA for specific EC2 operations<\/a><\/li>\n<li><a href=\"https:\/\/docs.aws.amazon.com\/IAM\/latest\/UserGuide\/reference_policies_examples_s3_full-access-except-production.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">Amazon S3: production bucket denied without recent MFA<\/a><\/li>\n<li><a href=\"https:\/\/docs.aws.amazon.com\/IAM\/latest\/UserGuide\/id_credentials_mfa_configure-api-require.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">Secure API access with MFA<\/a><\/li>\n<li><a href=\"https:\/\/docs.aws.amazon.com\/cli\/latest\/userguide\/cli-authentication-user.html\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">Authenticating using IAM user credentials for the AWS CLI<\/a><\/li>\n<\/ul>\n<h2 id=\"recommended\"><span class=\"ez-toc-section\" id=\"Recommended\"><\/span>Recommended<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<ul>\n<li><a href=\"https:\/\/support.logmeonce.com\/hc\/en-us\/articles\/115012663307-Two-Factor-Authentication-Setup\" target=\"_blank\" rel=\"noopener\">Two-Factor Authentication Setup<\/a><\/li>\n<\/ul>\n\n<div style=\"font-size: 0px; height: 0px; line-height: 0px; margin: 0; padding: 0; clear: both;\"><\/div>","protected":false},"excerpt":{"rendered":"<p>How to use MFA with the AWS CLI: get temporary credentials, enforce MFA with IAM policies, and move to IAM Identity Center for passkeys and security keys.<\/p>\n","protected":false},"author":27,"featured_media":248430,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[19737],"tags":[7232,3765,2795,10933,781],"class_list":["post-71705","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-two-factor-authentication","tag-aws-2","tag-data-protection-2","tag-cli","tag-multi-factor-authentication","tag-security"],"acf":[],"_links":{"self":[{"href":"https:\/\/logmeonce.com\/resources\/wp-json\/wp\/v2\/posts\/71705","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/logmeonce.com\/resources\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/logmeonce.com\/resources\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/logmeonce.com\/resources\/wp-json\/wp\/v2\/users\/27"}],"replies":[{"embeddable":true,"href":"https:\/\/logmeonce.com\/resources\/wp-json\/wp\/v2\/comments?post=71705"}],"version-history":[{"count":1,"href":"https:\/\/logmeonce.com\/resources\/wp-json\/wp\/v2\/posts\/71705\/revisions"}],"predecessor-version":[{"id":248429,"href":"https:\/\/logmeonce.com\/resources\/wp-json\/wp\/v2\/posts\/71705\/revisions\/248429"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/logmeonce.com\/resources\/wp-json\/wp\/v2\/media\/248430"}],"wp:attachment":[{"href":"https:\/\/logmeonce.com\/resources\/wp-json\/wp\/v2\/media?parent=71705"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/logmeonce.com\/resources\/wp-json\/wp\/v2\/categories?post=71705"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/logmeonce.com\/resources\/wp-json\/wp\/v2\/tags?post=71705"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}