Эта страница переведена автоматически. Прочитайте оригинальную версию на английском здесь.
В рабочей среде ошибки WebAuthn часто вызывают путаницу, поскольку браузеры предоставляют небольшой набор имен DOMException (таких как NotAllowedError), которые могут означать множество базовых причин. Кроме того, подавляющее большинство «ошибок» — часто более 95% в оптимизированных масштабных развертываниях — на самом деле являются ожидаемым поведением (пользователь прервал запрос операционной системы на использование ключа доступа).
Экспериментируйте с passkey-флоу в Passkeys Debugger.
Важно: Из соображений конфиденциальности браузеры не делают различий между активной отменой пользователем и отсутствием ключа доступа. Однако в некоторых ситуациях и при наличии достаточного контекста, как в веб, так и на нативных платформах, некоторые из этих случаев можно различить с помощью сигналов, таких как время.
Если вам нужны канонические определения этих имен, начните с MDN DOMException. Условия, специфичные для WebAuthn, которые приводят к этим исключениям (и то, что браузеры обязаны обеспечивать), описаны в спецификации W3C Web Authentication.
Если вы относитесь ко всем ошибкам как к «багам», вы будете действовать неправильно:
NotAllowedErrorВ этой статье мы ответим на следующие вопросы:
NotAllowedError на категории, с которыми можно работать (отмена, таймаут, доступность)?Последние статьи
⚙️
Шпаргалка для разработчиков по ключам доступа (passkeys)
👤
Как удалить ключ доступа на Apple, Windows и Android
📖
Техническое объяснение Conditional UI (автозаполнение ключей доступа) для WebAuthn
📖
Полное руководство по ошибкам WebAuthn в рабочей среде (2026)
📖
Ключи доступа в Brave Browser (2026): что работает, а что ломается
NotAllowedError — это поверхностный сигнал, а не первопричина. Это может означать отмену, таймаут, «нет локальных учетных данных» или отсутствие активации пользователем в зависимости от контекста.NotAllowedError означает разные вещи во время входа в систему через Conditional UI, модального входа в систему, создания ключа доступа вручную, условного создания (conditional create) и автоматически запускаемого добавления.<1s), отмена пользователем (1-15s) и таймаут (30s+) — это принципиально разные категории.AbortError обычно является проблемой жизненного цикла/параллелизма (навигация, повторный рендеринг, несколько одновременных запросов).SecurityError почти всегда связана с конфигурацией/контекстом и редко встречается в зрелых производственных развертываниях.error.name, чтобы вы могли разделить ошибки на категории, которые можно фактически исправить.Если вам нужно просто быстро сопоставить данные для отладки, начните с этой таблицы. Она ориентирована на то, что команды на самом деле видят на дашбордах и в тикетах поддержки.
error.name | Что это обычно означает в рабочей среде | Что проверить для подтверждения | Первое действие (UX + инженерия) |
|---|---|---|---|
NotAllowedError | Пользователь закрыл окно, истекло время ожидания или несоответствие доступности, объединенные в одну категорию. Это самая большая группа ошибок в рабочей среде. | время до ошибки, появлялся ли QR/гибридный интерфейс, началась ли церемония с реального действия пользователя | Рассматривать как ожидаемое: восстановить UI + показать резервный вариант |
AbortError | Ваше приложение (или браузер) прервало церемонию | навигация/повторный рендеринг во время церемонии; одновременные вызовы WebAuthn; AbortController.abort() | Обеспечить один выполняемый запрос; предотвратить смену маршрута; обработать прерывание как обычный поток управления |
SecurityError | Контекст/политика не разрешены | стратегия origin + RP ID; iframe/встраивание; HTTPS; feature policy | Исправить конфигурацию RP ID/origin; проверить политики встраивания; обеспечить безопасный контекст |
InvalidStateError | Несоответствие состояния (часто дублирование регистрации) | регистрация или вход; excludeCredentials; существующие учетные данные на аутентификаторе | Рассматривать как «уже зарегистрирован»; скорректировать UX путь; исправить генерацию опций |
ConstraintError | Требования не могут быть удовлетворены | authenticatorAttachment, userVerification, требования к резидентному ключу | Ослабить ограничения или предоставить альтернативный путь/резервный вариант. Пример: отсутствие блокировки экрана на Android |
DataError | Входные данные неверного формата/несогласованы | кодировка base64url; форматы id/challenge/user handle | Исправить кодировку/сериализацию; добавить валидацию при генерации опций |
NotSupportedError | Платформа/браузер не поддерживает то, что вы запросили | версия ОС/браузера; допущения при проверке функций | Немедленно перейти к резервному варианту; записать сегмент; избегать показа призывов к действию для ключей доступа в неподдерживаемых средах |
UnknownError | Платформа/аутентификатор завершились сбоем по общей причине | всплески после обновления ОС; сборка устройства; проблемы с провайдером credential-manager | Лояльный к повторным попыткам UX; сбор номеров сборок; исследование всплесков в сегментах |
Одна вещь, которую легко упустить: одно и то же error.name может означать совершенно разные вещи в зависимости от типа операции. Держите контекст операции в голове, читая следующие разделы. На практике ошибки создания (регистрации) ключа доступа обычно значительно превышают по количеству ошибки входа — приведенная выше таблица применима к обоим случаям, но большая часть объема приходится на создание.
Далее мы подробнее рассмотрим NotAllowedError, потому что это то, что вы будете видеть чаще всего, и то, что команды чаще всего неверно истолковывают.
NotAllowedError часто выглядит так, будто «ключи доступа не сработали», но обычно это платформа сообщает вам, что пользователь не завершил работу с пользовательским интерфейсом ОС. Ключ в том, чтобы разделить ее на категории, с которыми вы сможете работать.
Что вы увидите в консоли браузера:
| Источник | Сообщение об ошибке |
|---|---|
| Chrome, Edge | NotAllowedError: The operation either timed out or was not allowed. See: https://www.w3.org/TR/webauthn-2/#sctn-privacy-considerations-client. |
| Safari, WebKit | NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission. |
| Safari, WebKit | NotAllowedError: This request has been cancelled by the user. |
| Chrome, Edge | NotAllowedError: The operation is not allowed at this time because the page does not have focus. |
| Safari, WebKit | NotAllowedError: The document is not focused. |
| Firefox | NotAllowedError: Operation failed. |
Все это всплывает как error.name === "NotAllowedError". Сообщение error.message отличается в зависимости от движка браузера и основной причины, но результат один: церемония не завершилась.
Это относится как к входу, так и к созданию ключа доступа. При входе (Conditional UI, модальное окно с или без allowList) NotAllowedError обычно означает, что пользователь не завершил церемонию. При создании ключа доступа та же ошибка возникает по другим причинам: пользователь закрыл диалоговое окно создания (условное создание не сработало, или страница потеряла фокус во время автоматически запускаемого добавления). Тип операции меняет значение ошибки и то, что вам следует с ней делать.
Время часто является недооцененным сигналом. Ошибка менее чем через секунду после клика обычно означает немедленный отказ (среда не может это сделать, документ не в фокусе, отсутствует возможность). Ошибка через несколько секунд — это отмена пользователем (он увидел диалоговое окно и решил не продолжать). Ошибка через 30+ секунд — это таймаут. На нативных платформах время особенно важно: обмен данными с аутентификатором, биометрические запросы и передача данных в менеджер учетных данных имеют характерную продолжительность, которая помогает вам отличить «не сработало» от «пользователь отошел». Вы по-прежнему не можете легко определить, существовал ли ключ доступа.
Вам не нужен идеальный сигнал. Вам нужно достаточно контекста, чтобы избежать одинакового отношения ко всем NotAllowedError. iOS/Safari уделяется особое внимание ниже, поскольку там есть уникальные ограничения (требования к активации пользователем в более ранних версиях), но по чистому объему ошибок Windows и браузеры на базе Chromium часто генерируют больше NotAllowedError, чем любая другая платформа. Эти сигналы часто позволяют пройти 80% пути:
| Сигнал | Вероятное значение | Что делать дальше |
|---|---|---|
Немедленный сбой (<1s) | Отказ среды: нет возможности, документ не в фокусе, поверхность условного создания недоступна | Проверить обнаружение функций; убедиться, что документ в фокусе; проверить, поддерживается ли операция на этой платформе |
| Быстрая отмена (1-3s) | Внезапный запрос / нет контекста | Изменить время появления запроса; добавить паузу после отмены |
| Отмена пользователем (3-15s) | Пользователь увидел диалоговое окно и решил не продолжать | Ожидаемый UX; восстановить UI + показать резервный вариант |
| Таймаут (30s+) | Церемония прервана по таймауту без действий пользователя | Классифицировать как «не завершено»; подумать, был ли замечен запрос |
| QR/гибридный UI появляется до сбоя | На этом устройстве нет локальных учетных данных. Примечание: надежное определение решений по QR-кодам до того, как они произойдут, требует интеллектуального уровня ключей доступа, который знает, существуют ли пригодные учетные данные на текущем устройстве. | Ограничить предложения ключей доступа; сделать «Использовать телефон» явным; уменьшить неожиданные QR-коды |
| Сосредоточено в iOS/Safari и инициировано без клика/касания | Отсутствует активация пользователем | Начинать церемонию с реального жеста пользователя |
| Во время условного создания или автодобавления | Автозаполнение недоступно, учетные данные уже существуют, или страница потеряла фокус. Ошибки условного создания могут появляться внезапно и в больших объемах при запуске функции, что делает это одним из крупнейших источников ошибок за одну ночь. | См. условное создание; проверить состояние видимости документа; использовать getClientCapabilities() для проверки поддержки conditionalCreate перед попыткой вызова |
Вот почему NotAllowedError редко должна быть видимой пользователю. Это не то сообщение, на которое пользователь может как-то отреагировать.
Разделение по контексту также является тем местом, где показатели успешности разделяются наиболее резко. Анализ успешности аутентификации с помощью ключей доступа Corbado Passkey Benchmark 2026 оценивает завершение в первом квартале 2026 года на уровне 55–95% для потоков с неизвестным устройством (identifier-first) в сравнении с 95–99% для возвратов с известных устройств в масштабных развертываниях B2C. Веб-версия iOS (identifier-first) достигает 85–95% завершения (% CDA 0–5%), веб-версия Android — 70–85% (% CDA 5–10%) и веб-версия macOS — 70–85% (% CDA 10–15%), в то время как веб-версия Windows находится на уровне 45–60% завершения (identifier-first) с % CDA 55–65% на Windows 11 и 40–55% на Windows 10. Чтение объема NotAllowedError без разделения контекстов известных и неизвестных устройств смешивает два фундаментально разных режима успешности.
Один нюанс, который имеет значение на практике: некоторые агенты пользователя могут выводить таймауты как TimeoutError, но многие команды по-прежнему видят, что на дашбордах таймауты сводятся к NotAllowedError. В любом случае рассматривайте таймауты как «церемония не завершена» и классифицируйте, используя время плюс контекст.
Когда окно ОС закрывается или истекает время ожидания, ваш UI должен немедленно восстановиться и отреагировать изящно. Практический набор правил:
Помимо базовых правил:
Если ваших «отмен» действительно много, следующим шагом будет устранение их первопричин: время запросов, неожиданные QR-коды и низкая доступность.
Начните с изменений, которые быстро улучшают метрики:
NotAllowedError. Начните с isUVPAA() в качестве самого базового ограничения, затем используйте getClientCapabilities() для более точных проверок (условное создание, условное получение, гибридный транспорт, платформенный аутентификатор). Имейте в виду, что API обнаружения могут сломаться при обновлениях ОС: в iOS 26.2 был выпущен баг WebKit, при котором isUVPAA() возвращает false во всех браузерах на базе WKWebView, хотя ключи доступа работают нормально, вызывая внезапные всплески NotAllowedError у 10-25% пользователей iOS (см. исправление обнаружения ключей доступа с помощью getClientCapabilities()).Имена ошибок — это движущаяся цель. Существуют текущие предложения по добавлению более детальных ошибок WebAuthn (например, чтобы отделить «нет доступных учетных данных» от «пользователь отменил»). По состоянию на февраль 2026 года это не реализовано ни в одном браузере, поэтому по-прежнему стоит создавать собственные корзины причин на основе контекста и времени. Если вы хотите отслеживать эту работу, см. WebAuthn issue #2062 и объяснение "Новые коды ошибок".
Остальные имена ошибок встречаются реже, но их все равно стоит понимать, когда они появляются.
AbortError редко встречается в больших объемах по сравнению с NotAllowedError, но когда она появляется, она очень диагностична: обычно это означает, что церемония не была завершена, потому что ваше приложение сделало запрос недействительным — произошла навигация, изменилось состояние или начался второй запрос.
Что вы увидите в консоли браузера:
| Источник | Сообщение об ошибке |
|---|---|
| Chrome, Edge | AbortError: The operation was aborted. |
| Chrome, Edge | AbortError: Aborted by AbortSignal. |
| Firefox | AbortError: signal is aborted without reason |
| Firefox | AbortError: Operation timed out. |
| Safari, WebKit | AbortError: The user aborted a request. |
| Chrome | AbortError: CredentialContainer request is not allowed. |
Общие причины в рабочей среде включают:
AbortController.abort() во время повторных попыток или очистки состоянияЧтобы исправить это, сосредоточьтесь на том, чтобы сделать церемонию «критической секцией»:
Если вы видите AbortError, сконцентрированную во встроенных поверхностях или многодоменных приложениях, следующая категория для проверки — это SecurityError.
SecurityError — это браузер, говорящий вам: «этому контексту не разрешено делать то, что вы просили». На практике это почти всегда конфигурация, а не поведение пользователя. В зрелых производственных развертываниях SecurityError встречается редко, потому что эти проблемы обычно выявляются во время интеграционного тестирования. Если это появляется в рабочей среде, это обычно означает, что новый домен, контекст встраивания или цель развертывания были добавлены без надлежащей конфигурации WebAuthn.
Общие причины включают:
.well-known/webauthn или .well-known/assetlinks.json неправильно настроены, отсутствуют или временно недоступны. Проблемы с сетью во время критического окна, когда браузер запрашивает эти файлы, приведут к сбоям. Распространенное слепое пятно: если ваша домашняя страница недоступна из-за обслуживания, файлы well-known также не в сети, что ломает церемонии с ключами доступа во всех relying parties, которые от них зависят.Что вы увидите в консоли браузера:
| Источник | Сообщение об ошибке |
|---|---|
| Chrome, Edge | SecurityError: WebAuthn is not supported on sites with TLS certificate errors. |
| Любой браузер | SecurityError: The relying party ID is not a registrable domain suffix of, nor equal to the current origin's effective domain. |
| Chrome (iframe) | SecurityError: The 'publickey-credentials-create' feature is not enabled in this document. |
В рабочей среде SecurityError редки — они почти всегда выявляются во время интеграционного тестирования. Когда они появляются, ошибка сертификата TLS является наиболее частой причиной.
Самый быстрый цикл отладки:
publickey-credentials-create / publickey-credentials-get): MDN Permissions-PolicyКак только SecurityError обработана, следующая группа, к которой следует отнестись серьезно, — это набор ошибок, которые часто указывают на баги реализации: InvalidStateError, ConstraintError и DataError.

Шпаргалка по Passkeys. Практические рекомендации, шаблоны внедрения и KPI для программ passkeys.
Эти ошибки должны быть редкими в зрелой реализации ключей доступа. Когда они появляются, это обычно указывает на то, что генерация опций неверна для сегмента или что поток находится в неправильном состоянии.
Что вы увидите в консоли браузера:
| Источник | Сообщение об ошибке |
|---|---|
| Safari, WebKit | InvalidStateError: The user attempted to register an authenticator that contains one of the credentials already registered with the relying party. |
| Chrome, Edge | InvalidStateError: At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator. |
| Chrome, Edge | InvalidStateError: A request is already pending. |
| Firefox | InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable |
Типичные значения:
Практическая обработка:
excludeCredentials содержит список всех существующих идентификаторов учетных данных для пользователя, чтобы аутентификатор мог обнаружить дубликаты (см. excludeCredentials)InvalidStateError является ожидаемой и должна молча игнорироваться: это означает, что ключ доступа уже существует у провайдера. То же самое относится к NotAllowedError и AbortError во время условного создания (см. условное создание в Chrome)Типичное значение: аутентификатор не может удовлетворить ваши запрошенные ограничения.
Общие триггеры:
authenticatorAttachment или предположения о резидентном ключеuserVerification в сегментах, где они недоступныИсправление: ослабьте ограничения (где это приемлемо) или предоставьте альтернативный путь. В случае отсутствия блокировки экрана рассмотрите возможность обнаружения этого условия и направления пользователей, а не молчаливого сбоя (Android).
Типичное значение: входные данные имеют неверный формат или непоследовательны.
Общие триггеры:
Исправление: проверяйте и нормализуйте входные данные на границе, где вы генерируете параметры WebAuthn. На практике DataError фактически отсутствует в зрелых производственных системах — если генерация ваших параметров протестирована, вы не увидите это на дашбордах.
Если эти ошибки находятся под контролем, следующий вопрос — это покрытие: происходят ли сбои у пользователей из-за того, что окружение не может выполнить WebAuthn так, как вы ожидаете?
NotSupportedError — это сигнал о покрытии, а не о надежности. Обычно это означает, что сегмент не может выполнить то, что вы просили (ОС/браузер слишком старые, отсутствует возможность, функция не включена).
Что вы увидите в консоли браузера:
| Источник | Сообщение об ошибке |
|---|---|
| Chrome, Edge | NotSupportedError: The user agent does not support public key credentials. |
| Firefox | NotSupportedError: Resident credentials or empty 'allowCredentials' lists are not supported. |
| Chrome, Edge, Firefox | TypeError: PublicKeyCredential.parseCreationOptionsFromJSON is not a function |
| Chrome, Edge, Firefox | TypeError: PublicKeyCredential.parseRequestOptionsFromJSON is not a function |
| Chrome, Edge, Firefox | TypeError: credential.toJSON is not a function |
| Safari | TypeError: Can only call PublicKeyCredential.toJSON on instances of PublicKeyCredential |
Первые два — это подлинные NotSupportedError DOMExceptions. Записи TypeError технически являются другим типом исключения, но представляют тот же класс проблем: браузер или среда не поддерживают то, что вы запросили. Семейство JSON сериализации TypeError на практике встречается гораздо чаще, чем само исключение NotSupportedError (см. ниже).
Общие причины включают:
Семейство сериализации JSON является крупнейшим источником сбоев класса NotSupportedError в рабочей среде. Технически они проявляются как TypeError (отсутствующий метод), а не как DOMException, но именно здесь вы с ними столкнетесь. Две разные первопричины:
navigator.credentials, но нет PublicKeyCredential.parseCreationOptionsFromJSON / parseRequestOptionsFromJSON. На это приходится примерно 90% этого семейства ошибок, сосредоточенных в старых версиях Safari и Chrome. Если ваша клиентская библиотека зависит от этих методов без резервного варианта, это создает значительный объем ошибок..toJSON(). Расширения, такие как Bitwarden, LastPass или 1Password, могут перехватить церемонию и вернуть объект, который выглядит как учетные данные, но не является реальным экземпляром PublicKeyCredential. Вызов .toJSON() для него либо вызывает исключение, либо возвращает undefined, либо объект вообще является null. Это составляет примерно 10% семейства, но их особенно сложно отлаживать, потому что сообщения об ошибках различаются в зависимости от браузера (Safari: "Can only call on instances of PublicKeyCredential"; Firefox: "does not implement interface PublicKeyCredential").Обработка должна быть прямой и быстрой:
Если с покрытием всё в порядке, но сбои всё еще происходят в конкретных сегментах, возможно, вы имеете дело с проблемами на уровне платформы, проявляющимися как UnknownError.
UnknownError — это универсальная категория для сбоев аутентификатора/ОС, которые не вписываются в другие категории. Это часто временно, но также может возрастать после обновлений ОС.
Что вы увидите в консоли браузера:
| Источник | Сообщение об ошибке |
|---|---|
| Chrome (Android) | UnknownError: An unknown error occurred while talking to the credential manager. |
| Любой браузер | UnknownError: The operation failed for an unknown transient reason. |
| Любой браузер | UnknownError: Either the device has received unexpected request data, or the device has been reconfigured since the request was made. |
| Любой браузер | UnknownError: Something went wrong. |
| Chrome (LastPass) | TypeError: Cannot use 'in' operator to search for 'type' in null |
| Safari (LastPass) | TypeError: null is not an Object. (evaluating 'key in input') |
| Chrome (Bitwarden) | FallbackRequested |
Практическая обработка:
Один нишевый источник ошибок, который не вписывается ни в одну категорию DOMException: расширения браузера для управления паролями (такие как Bitwarden, LastPass, 1Password и другие) могут перехватывать вызовы API WebAuthn и возвращать нестандартные ответы. Несмотря на то, что их объем невелик по сравнению с отменами пользователей, их стоит отслеживать, поскольку они стабильно влияют на определенные сегменты пользователей, а симптомы запутывают: отсутствие методов в возвращаемом объекте учетных данных, неожиданные типы ошибок или некорректные ответы, которые не соответствуют ни одной документированной ошибке WebAuthn. Часто они появляются как UnknownError или как неклассифицированные исключения. Если вы видите всплески ошибок, сосредоточенные в конкретных браузерах без объяснения на уровне ОС, проверьте, не задействовано ли расширение менеджера учетных данных.
До сих пор мы говорили об именах ошибок в веб-браузерах. Но если вы также поставляете нативные приложения, ландшафт ошибок другой — и в некоторых отношениях значительно лучше.
Всё вышеперечисленное касается веб-браузеров. Нативные приложения — iOS с фреймворком ASAuthorization, Android с Credential Manager — имеют те же базовые категории ошибок, но отличаются важными деталями:
«Нет учетных данных» — это отдельный сигнал. В вебе браузеры объединяют «нет доступных учетных данных» и «отменено пользователем» в одно и то же NotAllowedError из соображений конфиденциальности. На нативных платформах использование preferImmediatelyAvailableCredentials на iOS (ASAuthorizationController) или setPreferImmediatelyAvailableCredentials(true) на Android (GetCredentialRequest) сообщает ОС показывать только учетные данные, которые уже есть на устройстве, и немедленно завершаться сбоем, если их нет. Это дает чистый результат «нет учетных данных», который не может дать веб.
Статус провайдера учетных данных виден. Нативные платформы при некоторых условиях могут сообщить вам, когда провайдер учетных данных (Google Password Manager, iCloud Keychain, 1Password и т. д.) не установлен, не настроен или не установлен по умолчанию, и отреагировать на это. В вебе эта информация скрыта за непрозрачными сообщениями NotAllowedError.
Сообщения об ошибках более конкретны. Поскольку пользователь установил приложение — и тем самым установил доверительные отношения с проверяющей стороной — ОС предоставляет больше диагностических деталей. Соображения конфиденциальности, которые заставляют веб-браузеры быть неопределенными, не применяются так же, когда приложение уже находится на устройстве. iOS возвращает локализованные сообщения на языке устройства пользователя. Android возвращает структурированные типы ошибок с цепочками причин. Это упрощает отладку, но означает, что ваша обработка ошибок должна учитывать локализацию и форматы ошибок, специфичные для платформы.
iOS передает ошибки ключей доступа через фреймворк ASAuthorization. Все ошибки приходят в callback делегата authorizationController(controller:didCompleteWithError:) как объекты NSError.
Классифицируйте по домену + коду, а не по строке сообщения. Основной домен ошибки — com.apple.AuthenticationServices.AuthorizationError (ASAuthorizationError.errorDomain). Приведите ошибку с помощью let nsError = error as NSError и сопоставляйте по .domain и .code. Никогда не сопоставляйте по .localizedDescription в рабочей среде — сообщения Apple локализуются на более чем 30 языков и могут меняться в разных версиях ОС. Строки сообщений, перечисленные ниже, полезны для распознавания ошибок в журналах, но они не являются критериями классификации.
Публичные коды ASAuthorizationError:
| Код | Имя | С | Что это значит |
|---|---|---|---|
| 1000 | Unknown | iOS 13 | Не должно появляться в рабочей среде. Общая категория. |
| 1001 | Canceled | iOS 13 | Пользователь закрыл окно ключей доступа. Самая частая ошибка в целом — эквивалент NotAllowedError. Чистый сигнал с пустым userInfo и без базовой ошибки. |
| 1002 | InvalidResponse | iOS 13 | Повреждение на уровне фреймворка. Редко встречается на практике. |
| 1003 | NotHandled | iOS 13 | Ни один провайдер не обработал запрос. Проверьте entitlements и конфигурацию провайдера учетных данных. |
| 1004 | Failed | iOS 13 | Общий сбой. localizedDescription содержит реальную причину (например, "Application with identifier X is not associated with domain Y"). userInfo может содержать строку FailureReason, но NSUnderlyingErrorKey не всегда заполнен — сбои связи с доменом возвращают nil для базовой ошибки. |
| 1005 | NotInteractive | iOS 15 | Нет доступных учетных данных при использовании preferImmediatelyAvailableCredentials. Это чистый сигнал "не найдено" — iOS-эквивалент "на этом устройстве нет ключа доступа." Интерфейс не был показан. |
| 1006 | MatchedExcludedCredential | iOS 18 | Ключ доступа уже существует для этого RP на этом устройстве. Чистый сигнал для обнаружения дубликатов — пустой userInfo, нет NSUnderlyingErrorKey. Классификация работает без сопоставления строк. |
| 1007 | CredentialImport | iOS 18.2 | Не удалось импортировать учетные данные. |
| 1008 | CredentialExport | iOS 18.2 | Не удалось экспортировать учетные данные. |
| 1009 | PreferSignInWithApple | iOS 26 | Пользователь предпочитает Sign in with Apple вместо ключа доступа. Новое в iOS 26. |
| 1010 | DeviceNotConfiguredForPasskeyCreation | iOS 26 | На устройстве отсутствует код доступа или конфигурация iCloud Keychain. Известный баг симулятора iOS 26: возвращает 1010, даже когда конфигурация верна (не воспроизводится на физических устройствах). |
Самое важное различие для рабочей среды: начиная с iOS 18, дублирующиеся учетные данные возвращают свой собственный код ошибки 1006 (MatchedExcludedCredential). На iOS 17 и более ранних версиях дублирующиеся учетные данные были скрыты внутри кода 1004 (Failed). В iOS 18+ различие носит структурный характер (разный код ошибки), а не текстовый.
Общие ошибки времени выполнения (справочник уровня журнала):
Эти сообщения появляются в localizedDescription или в userInfo для конкретных сценариев сбоев. Используйте их для поиска в журналах и отладки, а не для программной классификации.
| Сообщение (Английская локаль) | Базовый код | Примечания |
|---|---|---|
Application with identifier <TeamID.BundleID> is not associated with domain X | 1004 (Failed) | Разрешение Associated Domains приложения не совпадает с проверяющей стороной. Исправьте файл apple-app-site-association на вашем сервере. |
Couldn't communicate with a helper application. | 1004 (Failed) | Расширение провайдера учетных данных не ответило. Временная ошибка — уместна повторная попытка. |
Request already in progress for specified application identifier. | 1004 (Failed) | Был отправлен дублирующийся запрос ASAuthorization, пока один находился в ожидании. Состояние гонки в приложении. |
Stolen Device Protection is enabled and biometry is required. | 1004 (Failed) | Защита украденного устройства в iOS 17+ блокирует биометрическую аутентификацию в незнакомых местах. Нельзя исправить со стороны разработчика, но стоит сообщить об этом пользователю. |
(AuthenticationServicesCore.ASCABLEClient.ClientError error 2.) | Отдельный домен | Сбой рукопожатия Bluetooth при кросс-устройственной аутентификации (гибридной/CABLE). |
(AuthenticationServicesCore.ASCABLEClient.ClientError error 3.) | Отдельный домен | Сбой подключения Bluetooth при кросс-устройственной аутентификации. |
Локализованные сообщения «нет учетных данных» (код 1005):
Когда установлено preferImmediatelyAvailableCredentials и ключ доступа не существует, iOS возвращает код 1005 (NotInteractive) с локализованным сообщением на языке устройства пользователя. Это уникально для нативных приложений — веб-браузеры никогда не выдают этот сигнал. Сообщение всегда начинается с The operation couldn't be completed. (Операция не может быть завершена.), за которым следует локализованный текст:
| Язык | Сообщение |
|---|---|
| Китайский (Упрощенный) | 没有可用于登录的凭证。 |
| Вьетнамский | Không có sẵn thông tin để đăng nhập. |
| Арабский | لا تتوفر بيانات اعتماد لتسجيل الدخول. |
| Испанский | No hay ninguna credencial disponible para iniciar sesión. |
| Китайский (Традиционный) | 沒有可用於登入的憑證。 |
| Корейский | 로그인을 위한 자격 증명이 없습니다. |
| Французский (Канада) | Aucun identifiant disponible pour la connexion. |
| Португальский (Бразилия) | Nenhuma credencial disponível para login. |
| Французский (Франция) | Aucune information d'identification n'est disponible pour procéder à la connexion. |
| Тайский | ไม่มีข้อมูลประจำตัวสำหรับเข้าสู่ระบบ |
| Итальянский | Non ci sono credenziali disponibili per l'accesso. |
| Нидерландский | Geen inloggegevens beschikbaar. |
| Японский | ログイン用の資格情報がありません。 |
| Турецкий | Oturum açmak için kullanılabilecek kimlik bilgisi yok. |
На устройствах с английской локалью обычно отсутствие учетных данных разрешается на уровне API до того, как фреймворк ASAuthorization вернет локализованную ошибку, поэтому выше нет английского варианта. Программно всегда сопоставляйте код 1005, а не парсите эти строки.
Android передает ошибки ключей доступа через Credential Manager API (androidx.credentials). Сообщения об ошибках включают основное сообщение и часто cause с дополнительными деталями. По сравнению с iOS, Android предоставляет более структурированные типы ошибок и более явные причины для проблем конфигурации.
Отмена пользователем и обнаружение учетных данных:
| Ошибка | Примечания |
|---|---|
User cancelled the operation | Пользователь закрыл запрос ключа доступа. Эквивалент NotAllowedError. Примечание: Credential Manager также возвращает User canceled the request (написание США) из другого пути кода — оба идентичны. |
Excluded credential matches existing credential | Ключ доступа уже существует для этого ID учетных данных. Эквивалент InvalidStateError. В отличие от iOS, сообщение отличается от отмены пользователем. |
No create options available. | Ни один подходящий провайдер учетных данных не может обработать запрос на создание. Обычно означает, что сервисы Google Play устарели или ни один провайдер учетных данных не поддерживает создание ключей доступа. |
Ошибки конфигурации и безопасности:
| Ошибка | Примечания |
|---|---|
Passkeys not supported for this app | Digital Asset Links (assetlinks.json) отсутствует или не содержит отпечаток сертификата подписи приложения. Эквивалент SecurityError. |
Https failed: respCode=301, url=https://<domain>/.well-known/assetlinks.json | Файл assetlinks.json возвращает редирект вместо HTTP 200. Android требует наличия файла по точному URL без редиректов. |
The incoming request cannot be validated | Credential Manager не может проверить запрос на соответствие Digital Asset Links. |
RP ID cannot be validated. | Идентификатор проверяющей стороны (RP ID) в параметрах WebAuthn не соответствует assetlinks.json. |
Screen lock is missing. | На устройстве не настроен PIN, графический ключ или биометрия. Ключи доступа требуют проверки пользователя. Эквивалент ConstraintError. |
Cannot find an eligible account. | Ни один аккаунт Google на устройстве не подходит для создания ключа доступа (редко, обычно пользовательские корпоративные настройки). |
Ошибки платформы и аутентификатора:
| Ошибка | Примечания |
|---|---|
Unsuccessful result from folsom activity. | Внутренний сбой сервисов Google Play. "Folsom" — это компонент GMS для операций с ключами доступа. Временная ошибка — уместна повторная попытка. |
Can't find the proper key to decrypt the private key from WebauthnCredentialSpecifics. | Синхронизированный ключ доступа существует, но устройство не может расшифровать его закрытый ключ. Состояние синхронизации Google Password Manager несогласованно — учетные данные были синхронизированы с другого устройства, но ключ расшифровки недоступен. Нельзя исправить со стороны разработчиков. |
Operation was interrupted (cause: The UI was interrupted - please try again.) | Пользовательский интерфейс Credential Manager был прерван другой активностью (входящий вызов, поворот экрана, приложение свернуто). Эквивалент AbortError. |
Unknown credential error | Универсальная категория, когда не применяется ни один конкретный тип ошибки. Обычно временная. |
timeout (cause: Canceled) | Время ожидания операции Credential Manager истекло до того, как пользователь завершил биометрическую проверку. |
Следующая диаграмма показывает, как все обсуждаемые выше слои — инфраструктура, окружение, тип операции, классификация и обнаружение — соединяются в единое целое. Это ментальная модель, которую вы должны иметь в виду при проектировании отслеживания ошибок.
Ключевой вывод: необработанное error.name имеет смысл только тогда, когда вы знаете, какой уровень окружения его произвел, какая операция выполнялась и была ли ошибка ожидаемой или непредвиденной. Разделы ниже описывают, что следует логировать и как на это реагировать.
Большую часть классификации ошибок, описанной в этой статье, можно выполнить только с помощью сигналов на стороне клиента. Фронтенд-SDK наблюдаемости собирает достаточно контекста, чтобы классифицировать подавляющее большинство ошибок WebAuthn. Именно так архитектурно устроен SDK наблюдаемости Corbado: слой на стороне клиента обрабатывает атрибуцию ошибок, время, контекст операции и обнаружение платформы. Логирование на стороне сервера добавляет второй уровень для сбоев, которые может видеть только бэкенд.
Ключевое требование: каждая попытка должна быть объединяема от начала до конца. Общий идентификатор корреляции (например, auth_flow_id) связывает контекст на стороне клиента с результатом проверки на сервере.
| Сигнал | Почему это важно |
|---|---|
error.name + нормализованная категория причины | Сырая ошибка браузера + ваша классификация |
| Тип операции | Conditional UI, модальный вход, создание вручную, условное создание, автодобавление. CDA (кросс-устройственная аутентификация) — это отдельный сложный зверь. |
| Полный контекст окружения | ОС + версия, браузер + версия, Марка/модель оборудования, Настройки аутентификатора (например, GPM включен?) |
| Контекст аутентификатора / менеджера учетных данных | Поломки расширений и провайдеров |
| Время до ошибки с начала операции | Немедленный отказ (<1s) против отмены пользователем (1-15s) против таймаута (30s+) |
| Сеть / статус подключения | Сетевые ошибки часто проявляются как ошибки клиента. Отслеживайте, был ли пользователь офлайн, и ставьте в очередь логи для отправки при восстановлении связи. |
| Появлялся ли QR/гибридный UI | Локальный сбой против сбоя кросс-устройства |
Идентификатор корреляции (auth_flow_id) | Объединение с логами сервера |
Сбои проверки на сервере происходят после того, как браузер возвращает учетные данные и подписанный challenge. Они должны быть структурированными ошибками с явными кодами, а не смешиваться в одной корзине с именами клиентских DOMException. См.: реализация сервера WebAuthn.
| Сигнал | Почему это важно |
|---|---|
| Несоответствие challenge / challenge с истекшим сроком действия | Проблемы со временем сеанса или атаки повторного воспроизведения |
| Несоответствие Origin / RP ID | Ошибки конфигурации нескольких доменов |
| Неверная подпись / учетные данные не найдены | Удаленные или поврежденные учетные данные. Частый случай: вход с Conditional UI с помощью ключа доступа, который пользователь уже удалил на стороне сервера. Используйте Signal API, чтобы синхронизировать списки учетных данных клиента и сервера. |
| Несоответствие User handle | Проблемы с сопоставлением учетных записей |
Идентификатор корреляции (auth_flow_id) | Объединение с контекстом на стороне клиента |
Если вам нужна полная модель воронки (отсевы по шагам и конверсия между шагами), см.: телеметрия ключей доступа для понимания отсевов.
Подпишитесь на наш Passkeys Substack, чтобы получать новости.
При наличии этих данных вывод становится простым: большинство «ошибок» становятся либо исправлениями UX, либо исправлениями покрытия, либо исправлениями конфигурации. Но создание и поддержание этой классификации собственными силами — это значительная постоянная работа.
Приведенный выше контрольный список логирования собирает необработанные сигналы. В рабочей среде в больших масштабах одного error.name недостаточно. Создание этой классификации самостоятельно — это значительная постоянная работа: сообщения об ошибках меняются с каждым выпуском браузера и ОС, провайдеры менеджеров паролей выпускают обновления, которые изменяют поведение церемоний, а новые сигнатуры ошибок появляются с запуском каждой функции.
error.name недостаточно#Одно и то же сообщение NotAllowedError может означать шесть разных вещей в зависимости от трех измерений, которые браузеры не разделяют для вас:
| Измерение | Что дают вам браузеры | Что вам на самом деле нужно | Пример |
|---|---|---|---|
| Контекст операции | NotAllowedError | Был ли это Conditional UI, модальный вход, создание вручную, условное создание или автодобавление? | Android возвращает одну и ту же «неизвестную ошибку» при закрытии входа (ожидаемо) и сбое создания (непредвиденно) |
| Время | Нет данных о продолжительности | Немедленный отказ (<1s) против отмены пользователем (1-15s) против таймаута (30s+) | 200 мс = отказ среды; 5с = пользователь увидел диалоговое окно и отменил; 35с = таймаут |
| Платформа + аутентификатор | Общее error.name | ОС, браузер, версия, менеджер учетных данных для каждой ошибки | «пользователь закрыл диалоговое окно» в Chrome и «автозаполнение недоступно» в Safari оба появляются как NotAllowedError |
Именно для решения этой проблемы создан SDK наблюдаемости Corbado. Это легковесная фронтенд-интеграция, которая работает поверх вашей существующей реализации ключей доступа, с любым сервером WebAuthn и любым IDP, и автоматически классифицирует каждую ошибку WebAuthn по всем трем измерениям:
| Возможность | Что она делает |
|---|---|
| Атрибуция ошибок | Фиксирует ОС, версию ОС, браузер, версию браузера и аутентификатор при каждой попытке церемонии |
| Режим операции | Связывает каждую ошибку с конкретной операцией (Conditional UI, модальный вход, создание вручную, условное создание, автодобавление), поэтому одно и то же сообщение NotAllowedError разрешается в разные первопричины |
| Время от начала действия | Записывает продолжительность от начала церемонии, чтобы без угадывания различать немедленные отказы, отмены пользователем и таймауты |
| Интеллектуальная классификация ошибок | Сопоставляет полный контекст ошибки (а не только имя), нормализуя различные окружения (ОС, оборудование, настройки). Определяет приоритет различных групп ошибок, таких как CDA или локальные, и отличает Ожидаемые (Отмена пользователем) от Непредвиденных (Сбой системы). |
| Фрагментация менеджеров паролей | Обнаруживает, когда расширения менеджеров учетных данных (Bitwarden, 1Password, LastPass) перехватывают церемонии и возвращают нестандартные ответы, отделяя сбои, вызванные расширением, от сбоев платформы |
Это уровень Observe: видимость того, что происходит, без изменения вашей реализации.
Консоль управления Corbado поддерживает два пути расследования:
Сверху вниз (от дашборда к первопричине):
Снизу вверх (от шаблонов ошибок к влиянию):
Оба пути сходятся: сверху вниз сообщает вам, что что-то не так, снизу вверх сообщает вам почему. AI Analytics Assistant связывает оба подхода, позволяя задавать вопросы естественным языком об ошибках и метриках внедрения.
Команды, которые хотят действовать на основе этих сигналов, могут перейти к уровню Adopt, который добавляет интеллект ключей доступа для автоматического ограничения церемоний, оптимизации запросов на регистрацию и восстановления сломанных состояний ключей доступа. Для регулируемых сред или гипермасштабных развертываний Enterprise добавляет однотенантный хостинг, интеграцию SIEM и конфигурацию, соответствующую PSD2.
Получите бесплатный whitepaper по passkeys для enterprise.
Имена ошибок WebAuthn — это не приговор. Это подсказки, и они становятся полезными только тогда, когда вы связываете их с типом операции, временем и контекстом платформы.
NotAllowedError), жизненный цикл приложения/параллелизм (AbortError), контекст безопасности/конфигурации (SecurityError) или баги опций/состояния (InvalidStateError, ConstraintError, DataError). Подавляющее большинство объема — это NotAllowedError, и большая его часть — ожидаемое поведение (пользователь закрыл запрос).NotAllowedError? Используйте время (немедленный отказ против отмены пользователем против таймаута), индикатор QR/гибридного режима (несоответствие доступности), контекст активации пользователем (особенно в iOS/Safari) и тип операции (Conditional UI против модального входа против создания ключа доступа против условного создания). Не относитесь ко всем NotAllowedError как к одному режиму сбоя.error.name во время входа с помощью Conditional UI — это совершенно другой сигнал, чем при условном создании или ручном создании ключа доступа (пользователь закрыл окно). Запись типа операции вместе с ошибкой — это то, что превращает общее NotAllowedError в категорию, с которой можно работать.error.name, тип операции, время до ошибки с начала операции, тип потока, отображался ли QR/гибридный UI, ОС/браузер/устройство (включая версии), идентификатор корреляции (auth_flow_id) и отказы проверки сервера в виде явных кодов.Два правила, которые применимы ко всем типам ошибок: никогда не показывайте необработанные ошибки браузера пользователям — всегда предоставляйте четкий резервный путь — и отделяйте локальные попытки от QR/гибридных попыток на разных устройствах, поскольку они терпят неудачу по разным причинам и требуют разных исправлений. В масштабе поддержание классификации ошибок в разных браузерах, версиях ОС и менеджерах учетных данных — это постоянная работа. Рассмотрите возможность использования SDK наблюдаемости с поддерживаемой библиотекой шаблонов, а не создавать ее с нуля.
Corbado — это Authentication Intelligence Platform для CIAM-команд, обеспечивающих аутентификацию пользователей в крупных масштабах. Мы показываем то, что не видят логи IDP и общие инструменты аналитики: какие устройства, версии ОС, браузеры и менеджеры учётных данных поддерживают passkey, почему регистрации не превращаются в логины, где сбоит WebAuthn-поток и когда обновление ОС или браузера тихо ломает вход — всё это без замены Okta, Auth0, Ping, Cognito или вашего собственного IDP. Два продукта: Corbado Observe добавляет наблюдаемость для passkey и любых других способов входа. Corbado Connect даёт managed passkey со встроенной аналитикой (рядом с вашим IDP). VicRoads использует passkey для более чем 5 млн пользователей с Corbado (+80 % активации passkey). Поговорить с экспертом по passkey →
В веб-браузерах оба случая объединяются в NotAllowedError из соображений конфиденциальности, поэтому их нельзя различить напрямую. Используйте время как показатель: ошибка менее 1 секунды обычно означает отказ среды или отсутствие возможности, тогда как 1-15 секунд предполагают, что пользователь увидел и закрыл диалоговое окно. В нативных приложениях iOS и Android установка preferImmediatelyAvailableCredentials перед запросом дает четкий сигнал об отсутствии учетных данных (код 1005 в iOS, GetCredentialRequest в Android) до отображения какого-либо пользовательского интерфейса.
В оптимизированных масштабных развертываниях ключей доступа более 95% регистрируемых ошибок WebAuthn являются ожидаемыми отменами со стороны пользователя, а не системными сбоями. Отслеживание необработанного количества error.name без отделения закрытия запроса пользователем от подлинных сбоев завышает показатели ошибок и скрывает реальные регрессии внутри объема NotAllowedError. Разделяйте подсчеты по типу операции и рассматривайте сегменты отмены пользователем отдельно от непредвиденных ошибок, таких как SecurityError, ConstraintError и DataError.
Код 1005 в iOS (NotInteractive) означает, что на устройстве не было доступно ключей доступа, когда preferImmediatelyAvailableCredentials было установлено в ASAuthorizationController, и пользователю не показывался пользовательский интерфейс. Это четкий сигнал «ключ доступа не существует на этом устройстве», который веб-браузеры не могут предоставить из-за ограничений конфиденциальности. Всегда классифицируйте по числовому коду, а не по localizedDescription, поскольку сообщения Apple локализуются на более чем 30 языков и могут меняться в разных версиях ОС.
Во время условного создания NotAllowedError, AbortError и InvalidStateError являются ожидаемыми результатами и их следует игнорировать, а не выводить как ошибки. NotAllowedError указывает на то, что автозаполнение недоступно или страница потеряла фокус, тогда как InvalidStateError означает, что ключ доступа уже существует у провайдера. Перед попыткой вызова используйте getClientCapabilities() для проверки поддержки conditionalCreate и проверьте состояние видимости документа, чтобы избежать завышения количества ошибок.
Регистрируйте тип операции (Conditional UI, модальный вход, создание вручную, условное создание или автодобавление), время до ошибки с начала операции, версию ОС, версию браузера, модель оборудования, появлялся ли QR/гибридный интерфейс, и идентификатор корреляции для связи с журналами на стороне сервера. Сбои проверки на сервере, такие как несоответствие challenge, недействительная подпись и не найденные учетные данные, должны регистрироваться как явные структурированные коды, а не смешиваться в одной корзине с именами клиентских DOMException. Эти сигналы вместе позволяют классифицировать подавляющее большинство ошибок в категории, пригодные для действий, без угадывания.
Посмотрите, как Corbado вписывается во внедрение passkeys и текущий стек аутентификации.
Открыть Console
Похожие статьи
Содержание