---
url: 'https://www.corbado.com/ru/blog/webauthn-errors'
title: 'Полное руководство по ошибкам WebAuthn в рабочей среде (2026)'
description: 'Узнайте, что означают распространенные ошибки WebAuthn, такие как NotAllowedError, в рабочей среде, и как их классифицировать по типу операции, времени и платформе.'
lang: 'ru'
author: 'Vincent Delitz'
date: '2026-07-03T07:09:59.143Z'
lastModified: '2026-07-03T07:10:43.595Z'
keywords: 'ошибки webauthn, NotAllowedError, AbortError, SecurityError, устранение неполадок WebAuthn, классификация ошибок ключей доступа, ошибки условного создания, ASAuthorizationError, коды ASAuthorizationError, androidx.credentials, ошибки ключей доступа Creden'
category: 'WebAuthn Know-How'
---

# Полное руководство по ошибкам WebAuthn в рабочей среде (2026)

## 1. Введение

В рабочей среде ошибки WebAuthn часто вызывают путаницу, поскольку браузеры предоставляют небольшой набор имен `DOMException` (таких как `NotAllowedError`), которые могут означать множество базовых причин. Кроме того, подавляющее большинство «ошибок» — часто более 95% в оптимизированных масштабных развертываниях — на самом деле являются **ожидаемым поведением** (пользователь прервал запрос операционной системы на использование ключа доступа).

> Важно: Из соображений конфиденциальности браузеры не делают различий между активной отменой пользователем и отсутствием ключа доступа. Однако в некоторых ситуациях и при наличии достаточного контекста, как в веб, так и на нативных платформах, некоторые из этих случаев можно различить с помощью сигналов, таких как время.

Если вам нужны канонические определения этих имен, начните с [MDN `DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException). Условия, специфичные для WebAuthn, которые приводят к этим исключениям (и то, что браузеры обязаны обеспечивать), описаны в спецификации [W3C Web Authentication](https://www.w3.org/TR/webauthn-3/).

Если вы относитесь ко всем ошибкам как к «багам», вы будете действовать неправильно:

- вы загрязните свои метрики ошибок обычными отменами
- вы пропустите реальные регрессии, скрытые в массе `NotAllowedError`
- вы выпустите пользовательский интерфейс, который будет запутывать пользователей, а не помогать им восстановиться

В этой статье мы ответим на следующие вопросы:

- Что чаще всего означают наиболее распространенные имена ошибок WebAuthn в реальном трафике?
- Как разделить `NotAllowedError` на категории, с которыми можно работать (отмена, таймаут, доступность)?
- Почему одна и та же ошибка означает разные вещи в зависимости от операции (вход с помощью Conditional UI, модальный вход, создание ключа доступа и условное создание)?
- Какой минимальный контекст вам следует собирать, чтобы сообщение «произошел сбой» превратилось в воспроизводимую проблему?

## Key Facts

- `NotAllowedError` — это **поверхностный сигнал**, а не первопричина. Это может означать отмену, таймаут, «нет локальных учетных данных» или отсутствие активации пользователем в зависимости от контекста.
- **Тип операции меняет значение.** Одно и то же сообщение `NotAllowedError` означает разные вещи во время входа в систему через Conditional UI, модального входа в систему, создания ключа доступа вручную, условного создания (conditional create) и автоматически запускаемого добавления.
- **Время от начала операции** — самый недооцененный сигнал: немедленно (`<1s`), отмена пользователем (1-15s) и таймаут (30s+) — это принципиально разные категории.
- `AbortError` обычно является проблемой жизненного цикла/параллелизма (навигация, повторный рендеринг, несколько одновременных запросов).
- `SecurityError` почти всегда связана с конфигурацией/контекстом и редко встречается в зрелых производственных развертываниях.
- «Имена ошибок браузера» и «отклонения проверки сервером» — это разные уровни. Отслеживайте отклонения сервера как явные коды, а не как общие сбои клиента.
- **Сырые имена ошибок сами по себе не позволяют предпринять никаких действий.** Всегда фиксируйте тип операции, время и контекст платформы вместе с `error.name`, чтобы вы могли разделить ошибки на категории, которые можно фактически исправить.
- **«Окружение» (Environment) — это больше, чем просто браузер + ОС.** Чтобы по-настоящему понять ошибки, вам нужно отслеживать полную комбинацию: версию ОС, клиент (браузер/версия приложения), настройки аутентификатора (например, статус iCloud/GPM) и конкретную модель устройства.
- **Ошибки входа — это P1, ошибки создания — P2.** В то время как ошибки создания (P2) часто имеют больший объем из-за отказов пользователей, ошибки входа (P1) блокируют доступ и требуют немедленного оповещения.

## 2. Шпаргалка для рабочей среды

Если вам нужно просто быстро сопоставить данные для отладки, начните с этой таблицы. Она ориентирована на то, что команды на самом деле видят на дашбордах и в тикетах поддержки.

| **`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`, потому что это то, что вы будете видеть чаще всего, и то, что команды чаще всего неверно истолковывают.

## 3. 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+ секунд — это таймаут. На нативных платформах время особенно важно: обмен данными с аутентификатором, биометрические запросы и передача данных в менеджер учетных данных имеют характерную продолжительность, которая помогает вам отличить «не сработало» от «пользователь отошел». Вы по-прежнему не можете легко определить, существовал ли ключ доступа.

### 3.1 Различение с помощью контекста

Вам не нужен идеальный сигнал. Вам нужно достаточно контекста, чтобы избежать одинакового отношения ко всем `NotAllowedError`. iOS/Safari уделяется особое внимание ниже, поскольку там есть уникальные ограничения (требования к активации пользователем в более ранних версиях), но по чистому объему ошибок Windows и браузеры на базе Chromium часто генерируют больше `NotAllowedError`, чем любая другая платформа. Эти сигналы часто позволяют пройти 80% пути:

| **Сигнал**                                                   | **Вероятное значение**                                                                                                                                                                                                                                                                               | **Что делать дальше**                                                                                                                                                                           |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Немедленный сбой (`<1s`)                                    | Отказ среды: нет возможности, документ не в фокусе, поверхность условного создания недоступна | Проверить обнаружение функций; убедиться, что документ в фокусе; проверить, поддерживается ли операция на этой платформе |
| Быстрая отмена (1-3s)                                           | Внезапный запрос / нет контекста | Изменить время появления запроса; добавить паузу после отмены |
| Отмена пользователем (3-15s)                                   | Пользователь увидел диалоговое окно и решил не продолжать | Ожидаемый UX; восстановить UI + показать резервный вариант |
| Таймаут (30s+)                                               | Церемония прервана по таймауту без действий пользователя | Классифицировать как «не завершено»; подумать, был ли замечен запрос |
| QR/гибридный UI появляется до сбоя | На этом устройстве нет локальных учетных данных. Примечание: надежное определение решений по QR-кодам до того, как они произойдут, требует [интеллектуального уровня ключей доступа](https://docs.corbado.com/corbado-connect/features/passkey-intelligence), который знает, существуют ли пригодные учетные данные на текущем устройстве. | Ограничить предложения ключей доступа; сделать «Использовать телефон» явным; уменьшить неожиданные QR-коды |
| Сосредоточено в iOS/Safari и инициировано без клика/касания | Отсутствует активация пользователем | Начинать церемонию с реального жеста пользователя |
| Во время условного создания или автодобавления | Автозаполнение недоступно, учетные данные уже существуют, или страница потеряла фокус. Ошибки условного создания могут появляться внезапно и в больших объемах при запуске функции, что делает это одним из крупнейших источников ошибок за одну ночь. | См. условное создание; проверить состояние видимости документа; использовать `getClientCapabilities()` для проверки поддержки `conditionalCreate` перед попыткой вызова |

Вот почему `NotAllowedError` редко должна быть видимой пользователю. Это не то сообщение, на которое пользователь может как-то отреагировать.

Разделение по контексту также является тем местом, где показатели успешности разделяются наиболее резко. [Анализ успешности аутентификации с помощью ключей доступа Corbado Passkey Benchmark 2026](https://www.corbado.com/passkey-benchmark-2026/passkey-authentication-success-rate) оценивает завершение в первом квартале 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`. В любом случае рассматривайте таймауты как «церемония не завершена» и классифицируйте, используя время плюс контекст.

### 3.2 Обработка UX: сделайте отмену обычным выходом

Когда окно ОС закрывается или истекает время ожидания, ваш UI должен немедленно восстановиться и отреагировать изящно. Практический набор правил:

- восстановить UI входа (никаких бесконечно крутящихся спиннеров)
- сохранить состояние идентификатора (не заставлять вводить заново)
- показать видимый резервный вариант на том же экране
- избегать пугающих баннеров для ожидаемых результатов

Помимо базовых правил:

- Рассматривайте первую отмену как нормальную и предложите повторить попытку со спокойным объяснением. Только после второй отмены следует предлагать резервные варианты (см. резервные варианты и восстановление ключей доступа).
- Разрешите до трех запросов на создание перед паузой, чтобы удивленные пользователи получили второй шанс (см. лучшие практики создания ключей доступа).
- Разделяйте счетчики ошибок между созданием и входом, чтобы сравнивать подобное с подобным.
- Сегментируйте процент ошибок по ОС, браузеру и устройству, чтобы вы могли обнаружить, где на самом деле возникают трудности (см. лучшие практики входа с помощью ключей доступа).

Если ваших «отмен» действительно много, следующим шагом будет устранение их первопричин: время запросов, неожиданные QR-коды и низкая доступность.

### 3.3 Инженерные исправления, снижающие NotAllowedError

Начните с изменений, которые быстро улучшают метрики:

- Исправьте время запросов и активацию пользователем (особенно на iOS/Safari): события WebAuthn, активируемые пользователем в Safari.
- Уменьшите количество сюрпризов с QR/гибридными интерфейсами: почему процессы использования ключей доступа показывают QR-коды и пользователи отказываются.
- Ограничивайте предложения, чтобы ключи доступа показывались тогда, когда они скорее всего сработают: предлагайте ключи доступа только тогда, когда это вероятно увенчается успехом.
- Используйте паттерны, избегающие запросов, когда нет локальных учетных данных: немедленное посредничество WebAuthn.
- **Обрабатывайте нестабильность сети:** Сетевые ошибки во время проверки часто проявляются как общие сбои на стороне клиента. Реализуйте автономную очередь для ваших журналов телеметрии, чтобы не потерять данные об ошибках, когда связь пользователя обрывается во время церемонии.
- **Ограничивайте церемонии с помощью API обнаружения**, чтобы сбои среды не увеличивали вашу корзину `NotAllowedError`. Начните с `isUVPAA()` в качестве самого базового ограничения, затем используйте `getClientCapabilities()` для более точных проверок (условное создание, условное получение, гибридный транспорт, платформенный аутентификатор). Имейте в виду, что API обнаружения могут сломаться при обновлениях ОС: в iOS 26.2 был выпущен баг WebKit, при котором `isUVPAA()` возвращает `false` во всех браузерах на базе WKWebView, хотя ключи доступа работают нормально, вызывая внезапные всплески `NotAllowedError` у 10-25% пользователей iOS (см. исправление обнаружения ключей доступа с помощью getClientCapabilities()).

### 3.4 Примечание о развитии имен ошибок

Имена ошибок — это движущаяся цель. Существуют текущие предложения по добавлению более детальных ошибок WebAuthn (например, чтобы отделить «нет доступных учетных данных» от «пользователь отменил»). По состоянию на февраль 2026 года это не реализовано ни в одном браузере, поэтому по-прежнему стоит создавать собственные корзины причин на основе контекста и времени. Если вы хотите отслеживать эту работу, см. [WebAuthn issue #2062](https://github.com/w3c/webauthn/issues/2062) и [объяснение "Новые коды ошибок"](https://github.com/w3c/webauthn/wiki/Explainer%3A-New-Error-Codes-%282024-Edition%29).

Остальные имена ошибок встречаются реже, но их все равно стоит понимать, когда они появляются.

## 4. AbortError: Операция была прервана

`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.` |

Общие причины в рабочей среде включают:

- несколько одновременных вызовов WebAuthn (два конфликтующих запроса)
- изменение маршрута/повторный рендеринг во время выполняемой церемонии
- вызов `AbortController.abort()` во время повторных попыток или очистки состояния

Чтобы исправить это, сосредоточьтесь на том, чтобы сделать церемонию «критической секцией»:

- разрешайте только один выполняемый запрос за раз
- блокируйте навигацию во время церемонии (или чисто отменяйте ее и восстанавливайте UI)
- рассматривайте прерывание как ожидаемый поток управления: покажите кнопку повтора и резервный метод

Если вы видите `AbortError`, сконцентрированную во встроенных поверхностях или многодоменных приложениях, следующая категория для проверки — это `SecurityError`.

## 5. SecurityError: WebAuthn не поддерживается на сайтах с ошибками сертификатов TLS

`SecurityError` — это браузер, говорящий вам: «этому контексту не разрешено делать то, что вы просили». На практике это почти всегда конфигурация, а не поведение пользователя. В зрелых производственных развертываниях `SecurityError` встречается редко, потому что эти проблемы обычно выявляются во время интеграционного тестирования. Если это появляется в рабочей среде, это обычно означает, что новый домен, контекст встраивания или цель развертывания были добавлены без надлежащей конфигурации WebAuthn.

Общие причины включают:

- Несоответствие RP ID / origin (многодоменные установки)
- ограничения встраивания cross-origin (iframe)
- небезопасный контекст (не HTTPS) или заблокированные разрешения/политики
- `.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 является наиболее частой причиной.

Самый быстрый цикл отладки:

- зарегистрируйте входные данные origin и RP ID, которые вы использовали
- воспроизведите в том же контексте (верхний уровень или iframe, рабочий домен или staging)
- если вы встраиваете вход, убедитесь, что политика разрешений настроена (например, `publickey-credentials-create` / `publickey-credentials-get`): [MDN Permissions-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Permissions-Policy/publickey-credentials-create)
- проверьте вашу стратегию доменов (см. связанные origins)
- если вы используете iframe, проверьте политики функций (см. ключи доступа в iframe)

Как только `SecurityError` обработана, следующая группа, к которой следует отнестись серьезно, — это набор ошибок, которые часто указывают на баги реализации: `InvalidStateError`, `ConstraintError` и `DataError`.

## 6. InvalidStateError, ConstraintError, DataError: рассматривайте как баги реализации

Эти ошибки должны быть редкими в зрелой реализации ключей доступа. Когда они появляются, это обычно указывает на то, что генерация опций неверна для сегмента или что поток находится в неправильном состоянии.

### 6.1 InvalidStateError: "учетные данные уже зарегистрированы для этой проверяющей стороны"

**Что вы увидите в консоли браузера:**

| **Источник**     | **Сообщение об ошибке**                                                                                                                                    |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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](https://developer.chrome.com/docs/identity/webauthn-conditional-create))

### 6.2 ConstraintError

Типичное значение: аутентификатор не может удовлетворить ваши запрошенные ограничения.

Общие триггеры:

- отсутствие блокировки экрана устройства (особенно на Android): платформа требует биометрической или PIN проверки, но на устройстве не настроен экран блокировки
- слишком строгие `authenticatorAttachment` или предположения о резидентном ключе
- жесткие требования `userVerification` в сегментах, где они недоступны

Исправление: ослабьте ограничения (где это приемлемо) или предоставьте альтернативный путь. В случае отсутствия блокировки экрана рассмотрите возможность обнаружения этого условия и направления пользователей, а не молчаливого сбоя (Android).

### 6.3 DataError

Типичное значение: входные данные имеют неверный формат или непоследовательны.

Общие триггеры:

- ошибки кодирования (base64 вместо base64url)
- недействительные форматы id учетных данных / challenge

Исправление: проверяйте и нормализуйте входные данные на границе, где вы генерируете параметры WebAuthn. На практике `DataError` фактически отсутствует в зрелых производственных системах — если генерация ваших параметров протестирована, вы не увидите это на дашбордах.

Если эти ошибки находятся под контролем, следующий вопрос — это покрытие: происходят ли сбои у пользователей из-за того, что окружение не может выполнить WebAuthn так, как вы ожидаете?

## 7. NotSupportedError: Агент пользователя не поддерживает учетные данные с открытым ключом

`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` (см. ниже).

Общие причины включают:

- старые версии браузеров/ОС, которые не поддерживают базовый WebAuthn
- запрос функций WebAuthn, недоступных на этой платформе
- попытка выполнить платформо-специфичные потоки на неподдерживаемых устройствах

**Семейство сериализации JSON является крупнейшим источником сбоев класса `NotSupportedError` в рабочей среде.** Технически они проявляются как `TypeError` (отсутствующий метод), а не как `DOMException`, но именно здесь вы с ними столкнетесь. Две разные первопричины:

1. **Браузер не поддерживает методы сериализации JSON WebAuthn.** В браузере есть `navigator.credentials`, но нет `PublicKeyCredential.parseCreationOptionsFromJSON` / `parseRequestOptionsFromJSON`. На это приходится примерно 90% этого семейства ошибок, сосредоточенных в старых версиях Safari и Chrome. Если ваша клиентская библиотека зависит от этих методов без резервного варианта, это создает значительный объем ошибок.
2. **Расширения менеджеров паролей ломают `.toJSON()`.** Расширения, такие как Bitwarden, LastPass или 1Password, могут перехватить церемонию и вернуть объект, который выглядит как учетные данные, но не является реальным экземпляром `PublicKeyCredential`. Вызов `.toJSON()` для него либо вызывает исключение, либо возвращает `undefined`, либо объект вообще является `null`. Это составляет примерно 10% семейства, но их особенно сложно отлаживать, потому что сообщения об ошибках различаются в зависимости от браузера (Safari: "Can only call on instances of PublicKeyCredential"; Firefox: "does not implement interface PublicKeyCredential").

Обработка должна быть прямой и быстрой:

- немедленно переходите к резервному паролю/OTP
- запишите сегмент, чтобы можно было количественно оценить пробелы в покрытии
- избегайте показа призывов к действию для ключей доступа в сегментах, которые постоянно будут давать сбой

Если с покрытием всё в порядке, но сбои всё еще происходят в конкретных сегментах, возможно, вы имеете дело с проблемами на уровне платформы, проявляющимися как `UnknownError`.

## 8. 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`                                                                                                                     |

Практическая обработка:

- используйте лояльный к повторным попыткам UX (не "вините пользователя")
- собирайте номера ОС/сборок и контекст менеджера/провайдера учетных данных, где это возможно
- следите за всплесками в конкретных сегментах после обновлений ОС

Один нишевый источник ошибок, который не вписывается ни в одну категорию `DOMException`: **расширения браузера для управления паролями** (такие как Bitwarden, LastPass, 1Password и другие) могут перехватывать вызовы API WebAuthn и возвращать нестандартные ответы. Несмотря на то, что их объем невелик по сравнению с отменами пользователей, их стоит отслеживать, поскольку они стабильно влияют на определенные сегменты пользователей, а симптомы запутывают: отсутствие методов в возвращаемом объекте учетных данных, неожиданные типы ошибок или некорректные ответы, которые не соответствуют ни одной документированной ошибке WebAuthn. Часто они появляются как `UnknownError` или как неклассифицированные исключения. Если вы видите всплески ошибок, сосредоточенные в конкретных браузерах без объяснения на уровне ОС, проверьте, не задействовано ли расширение менеджера учетных данных.

До сих пор мы говорили об именах ошибок в веб-браузерах. Но если вы также поставляете нативные приложения, ландшафт ошибок другой — и в некоторых отношениях значительно лучше.

## 9. Несколько слов о нативных приложениях (iOS и Android)

Всё вышеперечисленное касается веб-браузеров. Нативные приложения — iOS с фреймворком ASAuthorization, Android с Credential Manager — имеют те же базовые категории ошибок, но отличаются важными деталями:

1. **«Нет учетных данных» — это отдельный сигнал.** В вебе браузеры объединяют «нет доступных учетных данных» и «отменено пользователем» в одно и то же `NotAllowedError` из соображений конфиденциальности. На нативных платформах использование `preferImmediatelyAvailableCredentials` на iOS (`ASAuthorizationController`) или `setPreferImmediatelyAvailableCredentials(true)` на Android (`GetCredentialRequest`) сообщает ОС показывать только учетные данные, которые уже есть на устройстве, и немедленно завершаться сбоем, если их нет. Это дает чистый результат «нет учетных данных», который не может дать веб.

2. **Статус провайдера учетных данных виден.** Нативные платформы при некоторых условиях могут сообщить вам, когда провайдер учетных данных (Google Password Manager, iCloud Keychain, 1Password и т. д.) не установлен, не настроен или не установлен по умолчанию, и отреагировать на это. В вебе эта информация скрыта за непрозрачными сообщениями `NotAllowedError`.

3. **Сообщения об ошибках более конкретны.** Поскольку пользователь установил приложение — и тем самым установил доверительные отношения с проверяющей стороной — ОС предоставляет больше диагностических деталей. Соображения конфиденциальности, которые заставляют веб-браузеры быть неопределенными, не применяются так же, когда приложение уже находится на устройстве. iOS возвращает локализованные сообщения на языке устройства пользователя. Android возвращает структурированные типы ошибок с цепочками причин. Это упрощает отладку, но означает, что ваша обработка ошибок должна учитывать локализацию и форматы ошибок, специфичные для платформы.

### 9.1 iOS (фреймворк ASAuthorization)

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, а не парсите эти строки.

### 9.2 Android (Credential Manager API)

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 истекло до того, как пользователь завершил биометрическую проверку.                                                                                                                                                       |

## 10. Собираем все вместе: таксономия ошибок

Следующая диаграмма показывает, как все обсуждаемые выше слои — инфраструктура, окружение, тип операции, классификация и обнаружение — соединяются в единое целое. Это ментальная модель, которую вы должны иметь в виду при проектировании отслеживания ошибок.

```mermaid
flowchart TD
    %% Global Styles
    classDef expected fill:#e3f2fd,stroke:#1565c0,stroke-width:2px;
    classDef unexpected fill:#ffebee,stroke:#c62828,stroke-width:2px;
    classDef network fill:#fff3e0,stroke:#ef6c00,stroke-dasharray: 5 5;
    classDef env fill:#f3e5f5,stroke:#7b1fa2,stroke-width:1px;
    classDef action fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;

    %% --- LEFT SIDE: NETWORK & SERVER ---
    subgraph Infrastructure ["Инфраструктура (Сервер/Сеть)"]
        direction TB
        ServerErr["Ошибка на стороне сервера<br/>500 / Логическая ошибка"]:::network
        NetErr["Сетевая ошибка<br/>Таймаут / 400 Bad Request"]:::network

        ServerErr & NetErr -->|Проявляется как| ClientManifest["Общая ошибка на стороне клиента"]
    end

    %% --- CENTER: THE ENVIRONMENT STACK (From Sketch) ---
    subgraph Environment ["Окружение (На стороне клиента)"]
        direction TB

        %% Layer 1: Platform Type
        subgraph Type ["Слой 1: Платформа"]
            Web["Веб / Браузер"]
            Native["Нативное / Приложение"]
        end

        %% Layer 2: OS Context
        subgraph OS_Layer ["Слой 2: ОС и версия"]
            OS_Web["ОС: Windows, macOS, Linux"]
            OS_Nat["ОС: iOS, Android"]
            OS_Ver["Версия ОС / Настройки<br/>(Блокировка экрана, Биометрия)"]

            Web --> OS_Web
            Native --> OS_Nat
            OS_Web & OS_Nat --> OS_Ver
        end

        %% Layer 3: Client Software
        subgraph Client_Layer ["Слой 3: Клиентское ПО"]
            Browser["Браузер: Chrome, Safari, Edge<br/>+ Версия"]
            App["Нативное приложение / WebView<br/>+ Версия приложения"]

            OS_Ver --> Browser
            OS_Ver --> App
        end

        %% Layer 4: WebAuthn Operation
        subgraph Op_Layer ["Слой 4: Операция WebAuthn"]
            OpType{"Тип операции"}

            Login["Вход<br/>(navigator.credentials.get)"]
            Reg["Регистрация<br/>(navigator.credentials.create)"]

            Browser & App --> OpType
            OpType --> Login
            OpType --> Reg
        end

        %% Layer 5: Sub-types & Complexity
        subgraph Modality ["Слой 5: Модальность и сложность"]
            Sub_CUI["CUI / Условный UI"]
            Sub_Auto["Автоматическая операция"]
            Sub_CDA["CDA / Кросс-устройственная аутентификация"]

            Login & Reg --> Sub_CUI
            Login & Reg --> Sub_Auto
            Login & Reg --> Sub_CDA
        end
    end

    %% --- BOTTOM: CLASSIFICATION & ACTION (The "What here?" section) ---
    subgraph Analysis ["Анализ ошибок и выполнение"]
        direction TB

        %% Classification
        Classification{"Классификация"}

        Exp["Ожидаемая ошибка<br/>(Пользователь прервал церемонию)"]:::expected
        Unexp["Непредвиденная ошибка<br/>(Система/Сбой/Неизвестно)"]:::unexpected

        ClientManifest --> Classification
        Sub_CUI & Sub_Auto & Sub_CDA --> Classification

        Classification --> Exp
        Classification --> Unexp

        %% Detection Logic
        subgraph Detection ["Цели обнаружения"]
            P1["P1: Проблемы со входом"]
            P2["P2: Проблемы с созданием"]
            Baseline["Обнаружение дрейфа базовой линии<br/>(Увеличение ожидаемых)"]
            NewErr["Обнаружение новых аномалий<br/>(Увеличение непредвиденных)"]
        end

        Exp --> Baseline
        Unexp --> NewErr
        Exp & Unexp --> P1 & P2

        %% Actionable Outcomes
        subgraph Action ["Действия по смягчению последствий"]
            Fix["Развертывание Hotfix / Новой версии"]:::action
            Fallback["Активация автоматического резервного варианта<br/>(Отключение ключа доступа)"]:::action
        end

        P1 & P2 & NewErr --> Fix
        P1 & P2 & NewErr --> Fallback
    end

    %% Connectors
    Infrastructure -.-> Environment
```

Ключевой вывод: необработанное `error.name` имеет смысл только тогда, когда вы знаете, какой уровень окружения его произвел, какая операция выполнялась и была ли ошибка ожидаемой или непредвиденной. Разделы ниже описывают, что следует логировать и как на это реагировать.

## 11. Что регистрировать, чтобы ошибки можно было отлаживать

Большую часть классификации ошибок, описанной в этой статье, можно выполнить только с помощью сигналов на стороне клиента. Фронтенд-SDK наблюдаемости собирает достаточно контекста, чтобы классифицировать подавляющее большинство ошибок WebAuthn. Именно так архитектурно устроен SDK наблюдаемости Corbado: слой на стороне клиента обрабатывает атрибуцию ошибок, время, контекст операции и обнаружение платформы. Логирование на стороне сервера добавляет второй уровень для сбоев, которые может видеть только бэкенд.

Ключевое требование: каждая попытка должна быть объединяема от начала до конца. Общий идентификатор корреляции (например, `auth_flow_id`) связывает контекст на стороне клиента с результатом проверки на сервере.

### 11.1 Сигналы на стороне клиента (Frontend SDK)

| **Сигнал**                                 | **Почему это важно**                                                                                                                   |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `error.name` + нормализованная категория причины    | Сырая ошибка браузера + ваша классификация                                                                                              |
| Тип операции                             | Conditional UI, модальный вход, создание вручную, условное создание, автодобавление. **CDA (кросс-устройственная аутентификация)** — это отдельный сложный зверь.   |
| Полный контекст окружения                   | ОС + версия, браузер + версия, **Марка/модель оборудования**, **Настройки аутентификатора** (например, GPM включен?)                           |
| Контекст аутентификатора / менеджера учетных данных | Поломки расширений и провайдеров                                                                                              |
| Время до ошибки с начала операции         | Немедленный отказ (`<1s`) против отмены пользователем (1-15s) против таймаута (30s+)                                                                 |
| Сеть / статус подключения              | Сетевые ошибки часто проявляются как ошибки клиента. Отслеживайте, был ли пользователь офлайн, и **ставьте в очередь логи** для отправки при восстановлении связи. |
| Появлялся ли QR/гибридный UI              | Локальный сбой против сбоя кросс-устройства                                                                                                        |
| Идентификатор корреляции (`auth_flow_id`)            | Объединение с логами сервера                                                                                              |

### 11.2 Сигналы на стороне сервера (Backend Verification)

Сбои проверки на сервере происходят после того, как браузер возвращает учетные данные и подписанный challenge. Они должны быть структурированными ошибками с явными кодами, а не смешиваться в одной корзине с именами клиентских `DOMException`. См.: реализация сервера WebAuthn.

| **Сигнал**                               | **Почему это важно**                                                                                                                                                                             |
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Несоответствие challenge / challenge с истекшим сроком действия   | Проблемы со временем сеанса или атаки повторного воспроизведения                                                                                                                                                                |
| Несоответствие Origin / RP ID                  | Ошибки конфигурации нескольких доменов                                                                                                                                                                |
| Неверная подпись / учетные данные не найдены | Удаленные или поврежденные учетные данные. Частый случай: вход с Conditional UI с помощью ключа доступа, который пользователь уже удалил на стороне сервера. Используйте Signal API, чтобы синхронизировать списки учетных данных клиента и сервера. |
| Несоответствие User handle                     | Проблемы с сопоставлением учетных записей                                                                                                                                                                         |
| Идентификатор корреляции (`auth_flow_id`)          | Объединение с контекстом на стороне клиента                                                                                                                                                                  |

Если вам нужна полная модель воронки (отсевы по шагам и конверсия между шагами), см.: телеметрия ключей доступа для понимания отсевов.

[Watch on YouTube](https://www.youtube.com/watch?v=wnrXJzvBjsU)

При наличии этих данных вывод становится простым: большинство «ошибок» становятся либо исправлениями UX, либо исправлениями покрытия, либо исправлениями конфигурации. Но создание и поддержание этой классификации собственными силами — это значительная постоянная работа.

## 12. Больше, чем имена ошибок: как Corbado превращает сырые ошибки в практичные сигналы

Приведенный выше контрольный список логирования собирает необработанные сигналы. В рабочей среде в больших масштабах одного `error.name` недостаточно. Создание этой классификации самостоятельно — это значительная постоянная работа: сообщения об ошибках меняются с каждым выпуском браузера и ОС, провайдеры менеджеров паролей выпускают обновления, которые изменяют поведение церемоний, а новые сигнатуры ошибок появляются с запуском каждой функции.

### 12.1 Почему одного `error.name` недостаточно

Одно и то же сообщение `NotAllowedError` может означать шесть разных вещей в зависимости от трех измерений, которые браузеры не разделяют для вас:

| **Измерение**                | **Что дают вам браузеры** | **Что вам на самом деле нужно**                                                             | **Пример**                                                                                                 |
| ---------------------------- | -------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **Контекст операции**        | `NotAllowedError`          | Был ли это Conditional UI, модальный вход, создание вручную, условное создание или автодобавление? | Android возвращает одну и ту же «неизвестную ошибку» при закрытии входа (ожидаемо) и сбое создания (непредвиденно) |
| **Время**                   | Нет данных о продолжительности           | Немедленный отказ (`<1s`) против отмены пользователем (1-15s) против таймаута (30s+)                             | 200 мс = отказ среды; 5с = пользователь увидел диалоговое окно и отменил; 35с = таймаут                            |
| **Платформа + аутентификатор** | Общее `error.name`       | ОС, браузер, версия, менеджер учетных данных для каждой ошибки                               | «пользователь закрыл диалоговое окно» в Chrome и «автозаполнение недоступно» в Safari оба появляются как `NotAllowedError`    |

### 12.2 Что собирает SDK наблюдаемости Corbado

Именно для решения этой проблемы создан [SDK наблюдаемости Corbado](https://www.corbado.com/pricing). Это легковесная фронтенд-интеграция, которая работает поверх вашей существующей реализации ключей доступа, с любым сервером WebAuthn и любым IDP, и автоматически классифицирует каждую ошибку WebAuthn по всем трем измерениям:

| **Возможность**                       | **Что она делает**                                                                                                                                                                                                                                                        |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Атрибуция ошибок**                | Фиксирует ОС, версию ОС, браузер, версию браузера и аутентификатор при каждой попытке церемонии                                                                                                                                                                         |
| **Режим операции**                   | Связывает каждую ошибку с конкретной операцией (Conditional UI, модальный вход, создание вручную, условное создание, автодобавление), поэтому одно и то же сообщение `NotAllowedError` разрешается в разные первопричины                                                                             |
| **Время от начала действия**         | Записывает продолжительность от начала церемонии, чтобы без угадывания различать немедленные отказы, отмены пользователем и таймауты                                                                                                                                               |
| **Интеллектуальная классификация ошибок** | Сопоставляет полный контекст ошибки (а не только имя), нормализуя различные **окружения** (ОС, оборудование, настройки). Определяет приоритет различных групп ошибок, таких как **CDA** или локальные, и отличает **Ожидаемые (Отмена пользователем)** от **Непредвиденных (Сбой системы)**. |
| **Фрагментация менеджеров паролей**   | Обнаруживает, когда расширения менеджеров учетных данных (Bitwarden, 1Password, LastPass) перехватывают церемонии и возвращают нестандартные ответы, отделяя сбои, вызванные расширением, от сбоев платформы                                                                         |

Это уровень **Observe**: видимость того, что происходит, без изменения вашей реализации.

### 12.3 Два пути отладки: сверху вниз и снизу вверх

Консоль управления Corbado поддерживает два пути расследования:

**Сверху вниз (от дашборда к первопричине):**

1. **Мониторинг двух разных типов аномалий:**
    - **Увеличение количества ожидаемых ошибок (Дрейф базовой линии):** Увеличилось ли постепенно количество «отмен пользователем» в определенном окружении (например, iOS 18.2 на iPhone 15)? Это часто указывает на трудности UX, внесенные обновлением ОС.
    - **Увеличение количества непредвиденных ошибок (Всплески):** Совершенно новая ошибка или внезапный скачок сбоев. Это обычно указывает на критическое изменение (внутреннее обновление стека IDP) или регрессию в новой версии браузера.
2. **Расстановка приоритетов по влиянию:**
    - **P1: Проблемы со входом.** Если показатели успешности входа падают, подавайте сигнал немедленно.
    - **P2: Проблемы с созданием.** Отслеживайте тенденции, но не стоит будить инженеров из-за всплесков «отмен пользователем» в потоках создания.
3. **Детализация окружения:** Используйте детализированные измерения (оборудование, настройки аутентификации), чтобы определить, является ли проблема глобальной или специфичной для «устройств Samsung с Android 14».
4. **Смягчение последствий:** Если обнаружен критический всплеск, подготовьте **Kill Switch** (рубильник). Автоматически или вручную отключите ключи доступа для этого конкретного окружения и вернитесь к OTP/паролям, чтобы сохранить процент входов, пока вы разбираетесь в проблеме.

**Снизу вверх (от шаблонов ошибок к влиянию):**

1. Начните с представления классификации ошибок. Просмотрите классифицированные шаблоны ошибок и их объемы.
2. Уточняйте сопоставление ошибок по мере появления новых шаблонов (например, новая версия браузера выдает другое сообщение об ошибке).
3. Ошибки сопоставляются с изменениями KPI и отображаются в виде аннотаций на дашбордах, поэтому всплеск определенного шаблона ошибок автоматически связывается с метрикой, на которую он повлиял.

Оба пути сходятся: сверху вниз сообщает вам, что что-то не так, снизу вверх сообщает вам почему. [AI Analytics Assistant](https://www.corbado.com/ai-analytics-assistant) связывает оба подхода, позволяя задавать вопросы естественным языком об ошибках и метриках внедрения.

Команды, которые хотят действовать на основе этих сигналов, могут перейти к уровню **Adopt**, который добавляет [интеллект ключей доступа](https://docs.corbado.com/corbado-connect/features/passkey-intelligence) для автоматического ограничения церемоний, оптимизации запросов на регистрацию и восстановления сломанных состояний ключей доступа. Для регулируемых сред или гипермасштабных развертываний **Enterprise** добавляет однотенантный хостинг, интеграцию SIEM и конфигурацию, соответствующую PSD2.

## 13. Заключение

Имена ошибок WebAuthn — это не приговор. Это подсказки, и они становятся полезными только тогда, когда вы связываете их с типом операции, временем и контекстом платформы.

- **Что означают наиболее распространенные имена ошибок 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 наблюдаемости с поддерживаемой библиотекой шаблонов, а не создавать ее с нуля.

## Часто задаваемые вопросы

### Как отличить отмену запроса на использование ключа доступа пользователем от отсутствия ключа доступа на устройстве?

В веб-браузерах оба случая объединяются в `NotAllowedError` из соображений конфиденциальности, поэтому их нельзя различить напрямую. Используйте время как показатель: ошибка менее 1 секунды обычно означает отказ среды или отсутствие возможности, тогда как 1-15 секунд предполагают, что пользователь увидел и закрыл диалоговое окно. В нативных приложениях iOS и Android установка `preferImmediatelyAvailableCredentials` перед запросом дает четкий сигнал об отсутствии учетных данных (код 1005 в iOS, `GetCredentialRequest` в Android) до отображения какого-либо пользовательского интерфейса.

### Почему у меня такой высокий уровень ошибок WebAuthn, хотя ключи доступа, кажется, работают для большинства пользователей?

В оптимизированных масштабных развертываниях ключей доступа более 95% регистрируемых ошибок WebAuthn являются ожидаемыми отменами со стороны пользователя, а не системными сбоями. Отслеживание необработанного количества `error.name` без отделения закрытия запроса пользователем от подлинных сбоев завышает показатели ошибок и скрывает реальные регрессии внутри объема `NotAllowedError`. Разделяйте подсчеты по типу операции и рассматривайте сегменты отмены пользователем отдельно от непредвиденных ошибок, таких как `SecurityError`, `ConstraintError` и `DataError`.

### Что означает код 1005 ASAuthorizationError в iOS и как его использовать?

Код 1005 в iOS (`NotInteractive`) означает, что на устройстве не было доступно ключей доступа, когда `preferImmediatelyAvailableCredentials` было установлено в `ASAuthorizationController`, и пользователю не показывался пользовательский интерфейс. Это четкий сигнал «ключ доступа не существует на этом устройстве», который веб-браузеры не могут предоставить из-за ограничений конфиденциальности. Всегда классифицируйте по числовому коду, а не по `localizedDescription`, поскольку сообщения Apple локализуются на более чем 30 языков и могут меняться в разных версиях ОС.

### Как обрабатывать NotAllowedError во время условного создания (conditional create) или автоматически запускаемого добавления ключа доступа?

Во время условного создания `NotAllowedError`, `AbortError` и `InvalidStateError` являются ожидаемыми результатами и их следует игнорировать, а не выводить как ошибки. `NotAllowedError` указывает на то, что автозаполнение недоступно или страница потеряла фокус, тогда как `InvalidStateError` означает, что ключ доступа уже существует у провайдера. Перед попыткой вызова используйте `getClientCapabilities()` для проверки поддержки `conditionalCreate` и проверьте состояние видимости документа, чтобы избежать завышения количества ошибок.

### Какой контекст следует регистрировать вместе с error.name, чтобы ошибки WebAuthn можно было отлаживать?

Регистрируйте тип операции (Conditional UI, модальный вход, создание вручную, условное создание или автодобавление), время до ошибки с начала операции, версию ОС, версию браузера, модель оборудования, появлялся ли QR/гибридный интерфейс, и идентификатор корреляции для связи с журналами на стороне сервера. Сбои проверки на сервере, такие как несоответствие challenge, недействительная подпись и не найденные учетные данные, должны регистрироваться как явные структурированные коды, а не смешиваться в одной корзине с именами клиентских `DOMException`. Эти сигналы вместе позволяют классифицировать подавляющее большинство ошибок в категории, пригодные для действий, без угадывания.
