Фон:
У меня есть веб-приложение React (использующее aws-amplify
), которое подключается к пулу пользователей AWS Cognito или использует его для аутентификации.
Я пытаюсь включить MFA и, в частности, хочу, чтобы у моих пользователей была возможность использовать программный токен TOTP MFA (то есть приложение Google Authenticator или подобное).
Когда я настраиваю свой пул пользователей на использование MFA, я вынужден включить SMS MFA, а затем TOTP программного обеспечения является необязательным. В моем случае у меня включен TOTP.
В своем веб-приложении я добавил необходимый компонент через:
import { SelectMFAType } from 'aws-amplify-react';
# other code
<SelectMFAType authData={user} MFATypes={{ SMS: true, TOTP: true }} />
# other code
Если вы не знакомы с aws-amplify-react
и / или SelectMFAType
, этот компонент предоставляет элемент пользовательского интерфейса, в котором пользователь может выбрать, предпочитает ли он использовать SMS или программный TOTP в качестве метода MFA. Если они выбирают SMS, используется их ранее подтвержденный номер телефона, и все работает.
Если пользователь выбирает TOTP, ему отображается QR-код для сканирования в выбранном им приложении для проверки подлинности, а в поле ввода предлагается ввести 6-значное число из приложения для проверки подлинности для проверки TOTP. Все это очень стандартно для всех, кто использовал опцию TOTP MFA на любом другом веб-сайте. Если пользователь вводит правильный код из приложения, его выбор TOTP подтверждается.
Короче говоря, SelectMFAType
- это просто ярлык / замена для быстрого прототипирования и тестирования без необходимости создания специального компонента.
Проблема:
Теперь вот проблема и как ее воспроизвести. (Отправной точкой является пользователь, который только что включил TOTP.):
- Пользователь выходит из системы.
- Пользователь входит в систему.
- Если имя пользователя и пароль верны, пользователю будет предложено ввести TOTP.
- Если TOTP правильный, пользователь вошел в систему. Пока все работает отлично, но так не будет.
- Пользователь выходит из системы.
- Пользователь входит в систему.
- Если имя пользователя и пароль верны, пользователю будет предложено ввести SMS MFA и получить текстовое сообщение с 6-значным кодом. Это неожиданное поведение. Я ожидаю, что он продолжит запрашивать TOTP MFA из своего приложения-аутентификатора, если пользователь не изменит свой предпочтительный метод обратно на SMS.
- С этого момента у пользователя всегда будет запрашиваться только SMS MFA.
Между шагами 4 и 7 предпочтения пользователя не изменились. Абсолютно ничего не изменилось ни в приложении React, ни в настройках AWS User Pool.
Кроме того, если я опрашиваю пользователя с помощью команды aws cognito-idp admin-get-user --user-pool-id ${MY_ID} --username ${MY_USER_NAME}
CLI AWS, я могу подтвердить, что предпочтения MFA пользователя соответствуют моим ожиданиям:
{
<other irrelevant keys redacted>
"PreferredMfaSetting": "SOFTWARE_TOKEN_MFA",
"UserMFASettingList": [
"SMS_MFA",
"SOFTWARE_TOKEN_MFA"
]
}
Задача MFA - это ответ API от AWS Cognito, когда пользователь пытается пройти аутентификацию, и это либо SMS_MFA
, либо SOFTWARE_TOKEN_MFA
. В моем случае я получаю один SOFTWARE_TOKEN_MFA
вызов, но затем все будущие вызовы, вопреки моему желанию, возвращаются к SMS_MFA
.
Если я повторяю процесс настройки TOTP (удаляю запись из приложения для аутентификации, повторно проверяю и т. Д.), Я могу повторить все шаги еще раз. Под этим я подразумеваю, что MFA будет ожидать TOTP один раз, а затем снова вернется к SMS после этого первого раза.
Может ли кто-нибудь пролить свет на эту ситуацию? Вы испытали это? Это известная проблема / ошибка в AWS Cognito? Я делаю что-то неправильно? Мне кажется, что если бы это было сломано, было бы генерироваться довольно много шума, но я не могу найти никого с такой же проблемой.
Вещи, которые я пробовал:
- Подробно искал кого-нибудь с такой же проблемой здесь, в Google и на форумах AWS.
- Я писал сообщения на форумах AWS, но это никуда не делось: https://forums.aws.amazon.com/thread.jspa?threadID=324131.
- Создал пользовательский компонент для замены
SelectMFAType
в соответствии с теорией, что что-то не так с реализацией вaws-amplify-react
. Я вставлю этот пользовательский компонент в конце этого вопроса в качестве справки. - Полностью уничтожен и воссоздан пул пользователей AWS Cognito. Я подумал, что, возможно, он поврежден или не работает. Это не имело значения.
SetupTOTP.js
Компонент:
import React, { useContext, useEffect, useState } from 'react';
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContextText,
DialogTitle,
TextField,
} from '@material-ui/core';
import { Auth } from 'aws-amplify';
import { ToastsStore } from 'react-toasts';
import QRCode from 'qrcode.react';
import { AuthStateContext } from 'Context/auth-context';
const SetupTOTP = React.memo(props => {
const { open, handleClose } = props;
const { username } = useContext(AuthStateContext);
const [user, setUser] = useState(null);
const [qrCode, setQrCode] = useState('');
const [token, setToken] = useState('');
const handleSave = () => {
Auth.verifyTotpToken(user, token)
.then(() => {
Auth.setPreferredMFA(user, 'TOTP').then(() => {
ToastsStore.success('Token Verified Updated');
handleClose();
});
})
.catch(err => {
console.log(err);
ToastsStore.error(err.message);
});
};
useEffect(() => {
Auth.currentAuthenticatedUser().then(user => {
setUser(user);
});
}, []);
useEffect(() => {
if (username && user) {
Auth.setupTOTP(user).then(code => {
setQrCode(
`otpauth://totp/AWSCognito:${username}?secret=${code}&issuer=REDACTED`
);
});
}
}, [username, user]);
return (
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Change Password</DialogTitle>
<DialogContent>
<QRCode value={qrCode} />
<TextField
margin="dense"
id="name"
label="Verify Token"
type="text"
fullWidth
onChange={e => setToken(e.target.value)}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleSave} color="primary">
Save
</Button>
</DialogActions>
</Dialog>
);
});
export default SetupTOTP;