spinny:~/writing $ less passkeys-webauthn-passwordless-authentication.md
12Пароли — одна из тех вещей, которые мы стали нормой только потому, что живем с ними уже много лет. Пользователи забывают их, используют повторно, пишут там, где не следует. Команды должны управлять сбросами, политиками, хэшами, утечками, фишингом и поддержкой.34passkey не делают аутентификацию идеальной, но устраняют огромную проблему: серверу больше не нужно хранить секрет, доступный пользователю.56## Что происходит на самом деле78passkey — это учетные данные, основанные на WebAuthn. Когда пользователь создает его, устройство генерирует пару ключей:910- закрытый ключ, который остается на устройстве или в менеджере паролей;11- открытый ключ, который может сохранить сервер.1213При входе сервер не спрашивает "скажи мне пароль". Отправьте случайный вызов. Устройство подписывает его закрытым ключом. Сервер проверяет подпись с помощью открытого ключа.1415И это приятно: если база данных украдена, внутри нее нет паролей, которые можно было бы взломать. А если пользователь окажется в поддельном домене, passkey недействителен для этого домена. Это не просто удобство, это конкретная защита от фишинга.1617## Браузер действует как мост1819В браузере есть два основных API:2021- `navigator.credentials.create()`, чтобы создать passkey;22- `navigator.credentials.get()`, чтобы использовать его во время входа в систему.2324Но важная логика находится на сервере. Сервер должен сгенерировать запрос, временно сохранить его, проверить ответ, проверить источник и Relying Party ID, а затем создать сеанс.2526Клиентская часть должна быть почти скучной:2728```typescript29const options = await fetch('/api/passkeys/login/options').then((r) => r.json());3031const credential = await navigator.credentials.get({32 publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(options),33});3435await fetch('/api/passkeys/login/verify', {36 method: 'POST',37 headers: { 'Content-Type': 'application/json' },38 body: JSON.stringify(credential?.toJSON()),39});40```4142Если вы обнаружите, что используете шифрование WebAuthn вручную, остановитесь. Используйте надежную серверную библиотеку. Ошибки здесь — это не «милые баги», а дыры в аутентификации.4344## Что сохранять в базе данных4546Нет нужды спасать полмира. Обычно достаточно:4748- идентификатор удостоверения;49- открытый ключ;50- подключенный пользователь;51- любой счетчик проверки или метаданные;52- транспорты, если это полезно для UX;53- имя, выбранное пользователем;54- дата создания и последнее использование.5556Минимальная таблица может выглядеть так:5758```sql59create table passkey_credentials (60 id uuid primary key default gen_random_uuid(),61 user_id uuid not null references users(id),62 credential_id text not null unique,63 public_key text not null,64 name text,65 created_at timestamptz not null default now(),66 last_used_at timestamptz67);68```6970Затем я бы добавил журналы аудита и уведомления: если кто-то создаст новый passkey в моей учетной записи, я хочу об этом знать.7172## UX важнее, чем демо7374Демо-версия passkey всегда прекрасна: вы нажимаете, Face ID, и вы в игре. Реальный продукт сложнее.7576Кто-то меняет телефон. Кто-то использует заблокированный компьютер компании. Некоторые люди не понимают, почему браузер предлагает passkey. Кто-то теряет доступ к своему устройству.7778Вот почему я бы не стал начинать с фразы «с сегодняшнего дня больше никаких паролей для всех». Я бы начал так:79801. passkey опционально для внутренних пользователей;812. предложение создать его после успешного входа в систему;823. страница учетной записи для переименования и удаления passkey;834. четкий резервный вариант;845. постепенное внедрение в основной логин.8586Текст в интерфейсе должен быть простым. «Использовать экран блокировки вашего устройства» лучше, чем «аутентифицироваться с использованием резидентских учетных данных FIDO2».8788## Ошибки, которых я бы избегал8990Не создавайте проблем для клиента. Задание создается на сервере и должно быть проверено только один раз.9192Не доверяйте только идентификатору учетных данных. Вам необходимо подтвердить подпись, запрос, происхождение и Relying Party ID.9394Не удаляйте резервные варианты, пока не будет выполнен хороший процесс восстановления. Функция «Беспароль» не обязательно означает «если ты потеряешь свой телефон, ты выйдешь навсегда».9596Не рассматривайте passkey как кнопку исключительно внешнего интерфейса. Скрытие кнопки не является безопасностью: настоящая проверка происходит на стороне сервера.9798## Passkey-первый или passkey-дружественный?99100О новом продукте вы можете подумать passkey в первую очередь. Для существующего приложения я предпочитаю использовать passkey: добавьте passkey в качестве рекомендуемого метода, измерьте успех и проблемы, а затем спокойно уменьшите вес пароля.101102Идеальная миграция не ощущается. Пользователь обнаруживает, что вход в систему стал проще, а не то, что компания изменила протокол аутентификации.103104## Заключение105106passkey интересны тем, что они одновременно улучшают безопасность и UX, что бывает редко. Они не волшебная палочка: восстановление, совместимость, поддержка и развертывание еще предстоит тщательно спроектировать.107108Но основные изменения сильны. Перестаньте просить пользователей изобретать и защищать секреты. Вы позволяете устройству подписать криптографическое доказательство, привязанное к вашему домену. Меньше человеческой памяти, меньше фишинга, меньше сбросов паролей. Я бы сказал, что к ним стоит отнестись серьезно.109110## Источники111112- [MDN: Passkeys](https://developer.mozilla.org/en-US/docs/Web/Security/Authentication/Passkeys)113- [MDN: Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API)114- [W3C: Web Authentication](https://www.w3.org/TR/webauthn-3/)115- [passkeys.dev](https://passkeys.dev/)116
:Passkey и WebAuthn: вход без пароля, без магииlines 1-116 (END) — press q to close