Создайте пароль для входа в систему без пароля

Ключи доступа делают учетные записи пользователей более безопасными, простыми и удобными в использовании.

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

Ключи доступа синхронизируются между устройствами с помощью поставщиков ключей доступа, таких как Google Password Manager и iCloud Keychain.

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

Как работает создание пароля

Прежде чем пользователь сможет войти в систему с помощью ключа доступа, вам следует создать ключ доступа, связать его с учетной записью пользователя и сохранить его открытый ключ на своем сервере.

Вы можете попросить пользователей создать ключ доступа в одной из следующих ситуаций:

  • Во время или после регистрации.
  • После входа в систему.
  • После входа в систему с использованием ключа доступа с другого устройства (то есть [authenticatorAttachment](https://web.dev/articles/passkey-form-autofill#authenticator-attachment) является cross-platform ).
  • На специальной странице, где пользователи могут управлять своими ключами доступа.

Чтобы создать ключ доступа, вы используете API WebAuthn .

Четыре компонента процесса регистрации ключа доступа:

  • Серверная часть : хранит данные учетной записи пользователя, включая открытый ключ.
  • Интерфейс : взаимодействует с браузером и получает необходимые данные из серверной части.
  • Браузер : запускает ваш JavaScript и взаимодействует с API WebAuthn.
  • Поставщик ключа доступа : создает и сохраняет ключ доступа. Обычно это менеджер паролей, например Google Password Manager, или ключ безопасности.
Процесс создания и регистрации пароля
Процесс создания и регистрации пароля.

Прежде чем создавать ключ доступа, убедитесь, что система соответствует следующим предварительным требованиям:

  • Учетная запись пользователя проверяется безопасным методом (например, по электронной почте, телефону или федерации удостоверений) в течение достаточно короткого периода времени.

  • Интерфейсная и серверная части могут безопасно взаимодействовать для обмена учетными данными.

  • Браузер поддерживает WebAuthn и создание ключей доступа.

В следующих разделах мы можем показать вам, как проверить большинство из них.

Как только система соответствует этим условиям, происходит следующий процесс создания ключа доступа:

  1. Система запускает процесс создания ключа доступа, когда пользователь инициирует действие (например, нажимает кнопку «Создать ключ доступа» на странице управления ключами доступа или после завершения регистрации).
  2. Внешний интерфейс запрашивает у серверной части необходимые учетные данные, включая информацию о пользователе, запрос и идентификаторы учетных данных, чтобы предотвратить дублирование.
  3. Внешний интерфейс вызывает navigator.credentials.create() чтобы предложить поставщику ключей доступа устройства сгенерировать ключ доступа, используя информацию из серверной части. Обратите внимание, что этот вызов возвращает обещание.
  4. Устройство пользователя аутентифицирует пользователя с помощью биометрического метода, ПИН-кода или шаблона для создания ключа доступа.
  5. Поставщик ключей доступа создает ключ доступа и возвращает учетные данные открытого ключа во внешний интерфейс, разрешая обещание.
  6. Внешний интерфейс отправляет сгенерированные учетные данные открытого ключа на серверную часть.
  7. Серверная часть хранит открытый ключ и другие важные данные для будущей аутентификации.
  8. Серверная часть уведомляет пользователя (например, по электронной почте) о необходимости подтвердить создание ключа доступа и обнаружить потенциальный несанкционированный доступ.

Этот процесс обеспечивает безопасный и бесперебойный процесс регистрации ключей доступа для пользователей.

Совместимость

Большинство браузеров поддерживают WebAuthn с некоторыми небольшими недостатками. См. passkeys.dev для получения подробной информации о совместимости браузера и ОС.

Создать новый ключ доступа

Чтобы создать новый ключ доступа, интерфейс должен выполнить следующий процесс:

  1. Проверьте совместимость.
  2. Получить информацию из бэкэнда.
  3. Вызовите API WebAuth, чтобы создать ключ доступа.
  4. Отправьте возвращенный открытый ключ на серверную часть.
  5. Сохраните учетные данные.

В следующих разделах показано, как это можно сделать.

Проверьте совместимость

Прежде чем отображать кнопку «Создать новый ключ доступа», интерфейс должен проверить:

  • Браузер поддерживает WebAuthn с PublicKeyCredential .

Browser Support

  • Хром: 67.
  • Край: 18.
  • Фаерфокс: 60.
  • Сафари: 13.

Source

  • Устройство поддерживает аутентификатор платформы (может создать ключ доступа и аутентифицироваться с помощью ключа доступа) с помощью PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() .

Browser Support

  • Хром: 67.
  • Край: 18.
  • Фаерфокс: 60.
  • Сафари: 13.

Source

Browser Support

  • Хром: 108.
  • Край: 108.
  • Фаерфокс: 119.
  • Сафари: 16.

Source

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

// Availability of `window.PublicKeyCredential` means WebAuthn is usable.  
// `isUserVerifyingPlatformAuthenticatorAvailable` means the feature detection is usable.  
// `isConditionalMediationAvailable` means the feature detection is usable.  
if (window.PublicKeyCredential &&  
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&  
    PublicKeyCredential.isConditionalMediationAvailable) {  
  // Check if user verifying platform authenticator is available.  
  Promise.all([  
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),  
    PublicKeyCredential.isConditionalMediationAvailable(),  
  ]).then(results => {  
    if (results.every(r => r === true)) {  
      // Display "Create a new passkey" button  
    }  
  });  
}  

В этом примере кнопка Создать новый ключ доступа должна отображаться только в том случае, если соблюдены все условия.

Получить информацию из серверной части

Когда пользователь нажимает кнопку, извлеките необходимую информацию из серверной части для вызова navigator.credentials.create() .

В следующем фрагменте кода показан объект JSON с необходимой информацией для вызова navigator.credentials.create() :

// Example `PublicKeyCredentialCreationOptions` contents
{
  challenge: *****,
  rp: {
    name: "Example",
    id: "example.com",
  },
  user: {
    id: *****,
    name: "john78",
    displayName: "John",
  },
  pubKeyCredParams: [{
    alg: -7, type: "public-key"
  },{
    alg: -257, type: "public-key"
  }],
  excludeCredentials: [{
    id: *****,
    type: 'public-key',
    transports: ['internal'],
  }],
  authenticatorSelection: {
    authenticatorAttachment: "platform",
    requireResidentKey: true,
  }
}

Пары ключ-значение в объекте содержат следующую информацию:

  • challenge : вызов, сгенерированный сервером в ArrayBuffer для этой регистрации.
  • rp.id : идентификатор RP (идентификатор проверяющей стороны), домен и веб-сайт могут указывать либо свой домен, либо регистрируемый суффикс. Например, если источником RP является https://login.example.com:1337 , идентификатор RP может быть либо login.example.com , либо example.com . Если идентификатор RP указан как example.com , пользователь может пройти аутентификацию на login.example.com или на любом поддомене на example.com . Дополнительные сведения об этом см. в разделе «Разрешить повторное использование пароля на ваших сайтах с помощью запросов связанного происхождения» .
  • rp.name : имя RP (проверяющей стороны). Это устарело в WebAuthn L3, но включено по соображениям совместимости.
  • user.id : уникальный идентификатор пользователя в ArrayBuffer, создаваемый при создании учетной записи. Оно должно быть постоянным, в отличие от имени пользователя, которое можно редактировать. Идентификатор пользователя идентифицирует учетную запись, но не должен содержать никакой личной информации (PII) . Вероятно, у вас уже есть идентификатор пользователя в вашей системе, но при необходимости создайте его специально для ключей доступа, чтобы в нем не было какой-либо личной информации.
  • user.name : уникальный идентификатор учетной записи, который узнает пользователь, например адрес электронной почты или имя пользователя. Это будет отображаться в средстве выбора учетной записи.
  • user.displayName : обязательное, более удобное имя для учетной записи. Оно не обязательно должно быть уникальным и может быть именем, выбранным пользователем. Если на вашем сайте нет подходящего значения для включения сюда, передайте пустую строку. Это может отображаться в средстве выбора учетной записи в зависимости от браузера.
  • pubKeyCredParams : указывает поддерживаемые RP (проверяющей стороной) алгоритмы открытого ключа. Мы рекомендуем установить его на [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}] . Это определяет поддержку ECDSA с P-256 и RSA PKCS#1, и их поддержка обеспечивает полный охват.
  • excludeCredentials : список уже зарегистрированных идентификаторов учетных данных. Предотвращает регистрацию одного и того же устройства дважды, предоставляя список уже зарегистрированных идентификаторов учетных данных . Член transports , если он предусмотрен, должен содержать результат вызова getTransports() во время регистрации каждого учетного документа.
  • authenticatorSelection.authenticatorAttachment : установите для этого параметра значение "platform" вместе с hint: ['client-device'] если создание ключа доступа является обновлением пароля, например, в рекламной акции после входа в систему. "platform" указывает, что RP хочет использовать аутентификатор платформы (аутентификатор, встроенный в устройство платформы), который не запрашивает, например, вставку ключа безопасности USB. У пользователя есть более простой вариант создания пароля.
  • authenticatorSelection.requireResidentKey : установите для него логическое значение true . Обнаруживаемые учетные данные (резидентный ключ) сохраняют информацию о пользователе в ключе доступа и позволяют пользователям выбирать учетную запись при аутентификации.
  • authenticatorSelection.userVerification : указывает, является ли проверка пользователя с использованием блокировки экрана устройства "required" , "preferred" или "discouraged" . По умолчанию установлено значение "preferred" , что означает, что аутентификатор может пропустить проверку пользователя. Установите для этого параметра значение "preferred" или опустите это свойство.

Мы рекомендуем создать объект на сервере, закодировать ArrayBuffer с помощью Base64URL и получить его из внешнего интерфейса. Таким образом, вы можете декодировать полезную нагрузку с помощью PublicKeyCredential.parseCreationOptionsFromJSON() и передать ее непосредственно в navigator.credentials.create() .

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

// Fetch an encoded `PubicKeyCredentialCreationOptions` from the server.
const _options = await fetch('/webauthn/registerRequest');

// Deserialize and decode the `PublicKeyCredentialCreationOptions`.
const decoded_options = JSON.parse(_options);
const options = PublicKeyCredential.parseCreationOptionsFromJSON(decoded_options);
...

Вызовите API WebAuthn, чтобы создать ключ доступа.

Вызовите navigator.credentials.create() , чтобы создать новый ключ доступа. API возвращает обещание, ожидая взаимодействия пользователя с отображением модального диалога.

Browser Support

  • Хром: 60.
  • Край: 18.
  • Фаерфокс: 60.
  • Сафари: 13.

Source

// Invoke WebAuthn to create a passkey.
const credential = await navigator.credentials.create({
  publicKey: options
});

Отправьте возвращенные учетные данные открытого ключа на серверную часть.

После того как пользователь проверен с помощью блокировки экрана устройства, создается ключ доступа и разрешается обещание, возвращающее объект PublicKeyCredential во внешний интерфейс.

Обещание может быть отклонено по разным причинам. Вы можете обработать эти ошибки, проверив свойство name объекта Error :

  • InvalidStateError : на устройстве уже существует ключ доступа. Пользователю не будет показано диалоговое окно с ошибкой. Сайт не должен воспринимать это как ошибку. Пользователь хотел, чтобы локальное устройство было зарегистрировано, и оно зарегистрировано.
  • NotAllowedError : пользователь отменил операцию.
  • AbortError : операция была прервана.
  • Другие исключения : Произошло что-то неожиданное. Браузер показывает пользователю диалоговое окно с ошибкой.

Объект учетных данных открытого ключа содержит следующие свойства:

  • id : идентификатор созданного ключа доступа в кодировке Base64URL. Этот идентификатор помогает браузеру определить, имеется ли на устройстве соответствующий ключ доступа при аутентификации. Это значение должно храниться в базе данных на серверной стороне.
  • rawId : версия идентификатора учетных данных ArrayBuffer.
  • response.clientDataJSON : данные клиента, закодированные в ArrayBuffer.
  • response.attestationObject : объект аттестации, закодированный ArrayBuffer. Он содержит важную информацию, такую ​​как идентификатор RP, флаги и открытый ключ.
  • authenticatorAttachment : возвращает "platform" , когда эти учетные данные создаются на устройстве с ключом доступа.
  • type : в этом поле всегда установлено значение "public-key" .

Закодируйте объект с помощью метода .toJSON() , сериализуйте его с помощью JSON.stringify() а затем отправьте на сервер.

...

// Encode and serialize the `PublicKeyCredential`.
const _result = credential.toJSON();
const result = JSON.stringify(_result);

// Encode and send the credential to the server for verification.  
const response = await fetch('/webauthn/registerResponse', {
  method: 'post',
  credentials: 'same-origin',
  body: result
});
...

Сохраните учетные данные

После получения учетных данных открытого ключа на серверной стороне мы рекомендуем использовать серверную библиотеку или решение вместо написания собственного кода для обработки учетных данных открытого ключа.

Затем вы можете сохранить информацию, полученную из учетных данных, в базе данных для будущего использования.

В следующем списке приведены свойства, рекомендуемые для сохранения:

  • Идентификатор учетных данных : идентификатор учетных данных, возвращаемый вместе с учетными данными открытого ключа.
  • Имя учетной записи : имя учетной записи. Назовите его в честь созданного им поставщика ключей доступа, который можно идентифицировать по AAGUID .
  • Идентификатор пользователя : идентификатор пользователя, используемый для создания ключа доступа.
  • Открытый ключ : открытый ключ возвращается вместе с учетными данными открытого ключа. Это необходимо для проверки утверждения пароля.
  • Дата и время создания : запишите дату и время создания ключа доступа. Это полезно для идентификации ключа доступа.
  • Дата и время последнего использования : записывает дату и время последнего использования ключа доступа для входа в систему. Это полезно для определения того, какой ключ доступа пользователь использовал (или не использовал).
  • AAGUID : уникальный идентификатор поставщика ключа доступа.
  • Флаг возможности резервного копирования : true, если устройство имеет право на синхронизацию ключей доступа. Эта информация помогает пользователям идентифицировать синхронизируемые ключи доступа и привязанные к устройству (не синхронизируемые) ключи доступа на странице управления ключами доступа.

Следуйте более подробным инструкциям при регистрации ключа доступа на стороне сервера.

Сигнал, если регистрация не удалась

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

Чтобы предотвратить такую ​​ситуацию, вы можете сообщить поставщику ключей доступа неизвестный ключ доступа с помощью Signal API . Вызвав PublicKeyCredential.signalUnknownCredential() с идентификатором RP и идентификатором учетных данных, RP может сообщить поставщику ключа доступа, что указанные учетные данные были удалены или не существуют. Поставщик ключа доступа решает, как обращаться с этим сигналом, но если он поддерживается, ожидается, что связанный ключ доступа будет удален.

// Detect authentication failure due to lack of the credential
if (response.status === 404) {
  // Feature detection
  if (PublicKeyCredential.signalUnknownCredential) {
    await PublicKeyCredential.signalUnknownCredential({
      rpId: "example.com",
      credentialId: "vI0qOggiE3OT01ZRWBYz5l4MEgU0c7PmAA" // base64url encoded credential ID
    });
  } else {
    // Encourage the user to delete the passkey from the password manager nevertheless.
    ...
  }
}

Чтобы узнать больше об API Signal, прочтите статью Сохраняйте соответствие ключей доступа с учетными данными на вашем сервере с помощью API Signal .

Отправить уведомление пользователю

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

Контрольный список

  • Подтвердите пользователя (желательно по электронной почте или безопасным способом), прежде чем разрешить ему создавать ключ доступа.
  • Предотвратите создание дубликатов ключей доступа для одного и того же поставщика ключей доступа с помощью excludeCredentials .
  • Сохраните AAGUID, чтобы идентифицировать поставщика ключей доступа и присвоить имя учетным данным пользователя.
  • Сигнализируйте, если попытка зарегистрировать ключ доступа не удалась с помощью PublicKeyCredential.signalUnknownCredential() .
  • Отправьте уведомление пользователю после создания и регистрации пароля для его учетной записи.

Ресурсы

Следующий шаг: войдите в систему с помощью пароля через автозаполнение формы .