Сообщение об ошибке
InvalidStateError: The user attempted to register an authenticator that contains one of the credentials already registered with the relying party.
Выше приведён текст Chromium; другие браузеры формулируют ошибку иначе. Неизменным остаётся только имя DOMException — `InvalidStateError`.
WebAuthn navigator.credentials.create() with excludeCredentials — DOMException InvalidStateError (Chromium)
Что это означает
Регистрация отклонена: на аутентификаторе уже есть учётные данные из списка `excludeCredentials`, который вы передали. Доверяющие стороны передают этот список именно для того, чтобы один и тот же аутентификатор не был зарегистрирован дважды для одного аккаунта, поэтому обычно это ожидаемый результат — у пользователя уже есть рабочий ключ доступа на этом устройстве. Браузеры сообщают об этой ошибке только после того, как пользователь взаимодействовал с аутентификатором.
Почему это происходит
Пользователь повторно нажал «Добавить ключ доступа» на том же устройстве
частоСамый частый случай. У аккаунта уже есть учётные данные на этом аутентификаторе; правильный ответ — предложить пользователю войти с их помощью.
На этом устройстве появился синхронизируемый ключ доступа
частоiCloud Keychain, Google Password Manager и другие поставщики ключей доступа синхронизируют учётные данные. Ключ доступа, зарегистрированный на телефоне, есть и на ноутбуке, поэтому регистрация «ноутбука» исключается.
Список исключений содержит учётные данные других пользователей
иногдаОшибка в коде, из-за которой в список попадают все учётные данные (или учётные данные другого аккаунта, использующего то же устройство), блокирует регистрации, которые должны проходить успешно.
Как исправить
- 1.
Сообщите пользователю, что всё уже настроено
Обрабатывайте InvalidStateError как «здесь уже есть ключ доступа» и подводите пользователя ко входу с этим ключом.
js try { const credential = await navigator.credentials.create({ publicKey: options }); await saveCredential(credential); } catch (e) { if (e.name === "InvalidStateError") { showMessage("This device already has a passkey for your account. You can use it to sign in."); } else if (e.name !== "NotAllowedError") { throw e; } } - 2.
Исключайте только учётные данные текущего пользователя
Формируйте `excludeCredentials` из сохранённых идентификаторов учётных данных пользователя, который выполнил вход, и используйте для каждого аккаунта постоянный случайный `user.id`.
js // Server: exclude only the CURRENT user's credentials (base64url ids from your database). const options = await generateRegistrationOptions({ rpName: "Example", rpID, userName: user.email, userID: user.webauthnUserId, // stable per user, never the email excludeCredentials: user.passkeys.map((p) => ({ id: p.credentialId, transports: p.transports })), });