I tried resetting TOTP settings by directly calling Amazon Cognito's AdminDeleteSoftwareToken

I tried resetting TOTP settings by directly calling Amazon Cognito's AdminDeleteSoftwareToken

Amazon Cognito has added AdminDeleteSoftwareToken, which allows administrators to reset users' TOTP MFA settings. Although the AWS CLI and boto3 did not support it yet, we were able to invoke it directly as an HTTP request with SigV4 signature and verify its behavior.
2026.08.27

This page has been translated by machine translation. View original

Introduction

On August 26, 2026, Amazon Cognito added AdminDeleteSoftwareToken, which allows administrators to delete a user's TOTP software token. This API is designed to delete the token of a user who has lost their TOTP device and to reset MFA.

https://aws.amazon.com/about-aws/whats-new/2026/08/amazon-cognito-totp-reset/

Item Before AdminDeleteSoftwareToken was added After
Administrator TOTP reset for target user No dedicated admin API to delete existing TOTP tokens Can be deleted with AdminDeleteSoftwareToken
Recovery while keeping MFA mandatory Need to consider changing the user pool-wide MFA settings, or deleting and recreating the user Can delete the target user's token and start the re-setup flow from the MFA_SETUP returned on the next authentication
Scope of impact The entire user pool, or the target user's ID and related data The target user's software token

Prerequisites

Verification was performed in ap-northeast-1. After creating a user pool, software token MFA was enabled and MFA was set to required, and an app client and dummy user for password authentication were prepared. Since actual IDs and passwords are not included, variables are used below.

User Pool with Mandatory MFA

First, a user pool for password authentication was created.

USER_POOL_ID=$(aws cognito-idp create-user-pool \
  --pool-name totp-reset-test-pool \
  --policies '{"PasswordPolicy":{"MinimumLength":8,"RequireUppercase":true,"RequireLowercase":true,"RequireNumbers":true,"RequireSymbols":false}}' \
  --query 'UserPool.Id' --output text)

Software token MFA was enabled and the pool's MFA was set to required.

aws cognito-idp set-user-pool-mfa-config \
  --user-pool-id "$USER_POOL_ID" \
  --mfa-configuration ON \
  --software-token-mfa-configuration Enabled=true

App Client and Dummy User

An app client for password authentication was created.

CLIENT_ID=$(aws cognito-idp create-user-pool-client \
  --user-pool-id "$USER_POOL_ID" \
  --client-name totp-test-client \
  --no-generate-secret \
  --explicit-auth-flows \
    ALLOW_ADMIN_USER_PASSWORD_AUTH \
    ALLOW_REFRESH_TOKEN_AUTH \
    ALLOW_USER_PASSWORD_AUTH \
  --query 'UserPoolClient.ClientId' --output text)

A dummy user was created without sending a notification, and a permanent password was set.

USERNAME=testuser
PASSWORD='<TestUserPassword>'

aws cognito-idp admin-create-user \
  --user-pool-id "$USER_POOL_ID" \
  --username "$USERNAME" \
  --temporary-password "$PASSWORD" \
  --message-action SUPPRESS

aws cognito-idp admin-set-user-password \
  --user-pool-id "$USER_POOL_ID" \
  --username "$USERNAME" \
  --password "$PASSWORD" \
  --permanent

The dummy user became CONFIRMED after the password was set. Upon authentication, an MFA_SETUP challenge was returned.

TOTP Registration

For the dummy user with a permanent password set, an MFA_SETUP session was obtained and TOTP was registered.

MFA_SETUP_SESSION="$(aws cognito-idp admin-initiate-auth \
  --user-pool-id "$USER_POOL_ID" \
  --client-id "$CLIENT_ID" \
  --auth-flow ADMIN_USER_PASSWORD_AUTH \
  --auth-parameters "USERNAME=$USERNAME,PASSWORD=$PASSWORD" \
  --query Session --output text)"

ASSOCIATE_JSON="$(aws cognito-idp associate-software-token \
  --session "$MFA_SETUP_SESSION" \
  --output json)"
TOTP_SECRET="$(printf '%s' "$ASSOCIATE_JSON" | \
  python3 -c 'import json,sys; print(json.load(sys.stdin)["SecretCode"])')"
ASSOCIATE_SESSION="$(printf '%s' "$ASSOCIATE_JSON" | \
  python3 -c 'import json,sys; print(json.load(sys.stdin)["Session"])')"

To avoid installing oathtool in the local environment, a code was generated using oathtool inside a Docker container. TOTP_SECRET and the generated code are not displayed in standard output.

TOTP_CODE="$(docker run --rm -e "TOTP_SECRET=$TOTP_SECRET" alpine sh -c \
  'apk add --no-cache oath-toolkit-oathtool >/dev/null && \
   oathtool --totp --base32 "$TOTP_SECRET"')"

aws cognito-idp verify-software-token \
  --session "$ASSOCIATE_SESSION" \
  --user-code "$TOTP_CODE" \
  --query Status --output text

After SUCCESS was returned, it was confirmed that the SOFTWARE_TOKEN_MFA challenge is returned upon authentication.

API Verification

For the user with TOTP registered in the prerequisites, the same items were checked before and after deletion.

Confirmation Before Deletion

Authentication Factors

aws cognito-idp admin-get-user-auth-factors \
  --user-pool-id "$USER_POOL_ID" \
  --username "$USERNAME" \
  --query ConfiguredUserAuthFactors --output json
[
    "PASSWORD",
    "SOFTWARE_TOKEN"
]

Challenge During Authentication

aws cognito-idp admin-initiate-auth \
  --user-pool-id "$USER_POOL_ID" \
  --client-id "$CLIENT_ID" \
  --auth-flow ADMIN_USER_PASSWORD_AUTH \
  --auth-parameters "USERNAME=$USERNAME,PASSWORD=$PASSWORD" \
  --query ChallengeName --output text
SOFTWARE_TOKEN_MFA

Password and software token are set as authentication factors, and the TOTP challenge is returned upon authentication.

API Execution

At the time of writing, the latest AWS CLI 2.36.32 and boto3 1.43.81 did not support AdminDeleteSoftwareToken.

Instead of AWS CLI and boto3, botocore was used to add SigV4 signing and call directly.

import json
from urllib.request import Request, urlopen

from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
from botocore.session import get_session

region = 'ap-northeast-1'
payload = {
    'UserPoolId': 'ap-northeast-1_XXXXXXXXX',
    'Username': 'testuser',
}
request = AWSRequest(
    method='POST',
    url=f'https://cognito-idp.{region}.amazonaws.com/',
    data=json.dumps(payload, separators=(',', ':')).encode(),
    headers={
        'Content-Type': 'application/x-amz-json-1.0',
        'X-Amz-Target': 'AWSCognitoIdentityProviderService.AdminDeleteSoftwareToken',
    },
)

credentials = get_session().get_credentials().get_frozen_credentials()
SigV4Auth(credentials, 'cognito-idp', region).add_auth(request)
prepared = request.prepare()
with urlopen(
    Request(prepared.url, data=prepared.body, headers=dict(prepared.headers.items()), method='POST')
) as response:
    print(f'HTTP {response.status}')
    print(response.read().decode() or '{}')

Confirmation After Deletion

After executing AdminDeleteSoftwareToken, the authentication factors and the challenge during password authentication were confirmed using the same commands as before deletion.

Authentication Factors

[
    "PASSWORD"
]

The software token was deleted, and the authentication factor is now password only.

Challenge During Authentication

MFA_SETUP

On the next authentication, the MFA_SETUP challenge was returned, allowing a new software token to be set up.

Summary

For users who use only TOTP as an MFA factor without alternative factors such as SMS MFA, recovery when a device is lost has been a challenge. By using AdminDeleteSoftwareToken, it is possible to implement a recovery flow that resets only the target user's TOTP, returns to MFA_SETUP on the next authentication, and prompts re-registration.

If you have been postponing making MFA mandatory in Cognito due to concerns about recovery when a TOTP device is lost, please try this flow.

Going forward, it would be desirable to see official SDK support as well as APIs that allow administrators to reset authentication factors other than TOTP, including passkeys, on a per-user basis.

Share this article

AWSのお困り事はクラスメソッドへ