Повторная аутентификация с помощью ключа доступа

Веб-функции: Веб-аутентификация

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

Архитектура Delta Flow

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

Серверный

Разница в количестве сгенерированных вариантов

Создайте конечную точку, которая заполняет параметры разрешенных учетных данных специально для активного известного пользователя:

Ограничение учетных данных. Заполните массив параметров allowCredentials определенными записями PublicKeyCredentialDescriptor, сопоставив все зарегистрированные идентификаторы учетных данных для вошедшего в систему пользователя. Если оставить это поле пустым или пропустить его, будут использоваться обнаруживаемые учетные данные, что нарушает безопасность сеанса.

// Node.js step-up options generation example
router.post("/api/reauth/options", enforceActiveSession, async (req, res) => {
  const userPasskeys = await db.findCredentialsByUserId(req.user.id);

  const options = {
    challenge: serverGeneratedBase64UrlChallenge, // Random challenge stored in user session
    rpId: "example.com",
    // Enforce allowance strictly limited to the user's credentials list
    allowCredentials: userPasskeys.map((cred) => ({
      type: "public-key",
      id: cred.id,
      transports: cred.transports, // Speeds up resolution by indicating platform transports
    })),
  };
  return res.json(options);
});

Разница между конечными точками проверки

Проверьте утверждение, возвращенное клиентом:

Подтверждение права собственности на аккаунт. Конечная точка подтверждения ДОЛЖНА явно подтверждать, что полученный в результате аутентификации идентификатор учетных данных, возвращенный клиентом, разрешается в сохраненную запись учетных данных, связанный идентификатор пользователя которой строго соответствует активному вошедшему в систему пользователю (storedCredential.passkeyUserId === req.user.id). Если возвращается действительный ключ доступа другого пользователя, аутентификация ДОЛЖНА быть немедленно отклонена.

Различия в процессах на стороне клиента

Приложения выбирают один из двух интерфейсов повторной аутентификации в зависимости от интерфейса транзакции:

A. Кнопка (без полей ввода)

Запускайте повторную аутентификацию, когда пользователь нажимает кнопку "Подтвердить личность" или "Продолжить транзакцию".

<button id="reauth-btn" data-testid="reauth-button">Confirm Transaction</button>
let reauthAbortController = new AbortController();

async function triggerButtonReauth() {
  // Abort any background suggestion flows to avoid passkey prompt collisions
  reauthAbortController.abort();
  reauthAbortController = new AbortController();

  const optionsResponse = await fetch("/api/reauth/options", {
    method: "POST",
  });
  const optionsJSON = await optionsResponse.json();
  const publicKey =
    PublicKeyCredential.parseRequestOptionsFromJSON(optionsJSON);

  try {
    const credential = await navigator.credentials.get({
      publicKey,
      signal: reauthAbortController.signal,
    });

    if (credential) {
      const encodedCredential = credential.toJSON();
      const verifyResponse = await fetch("/api/reauth/verify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(encodedCredential),
      });

      if (verifyResponse.ok) {
        showTransactionSuccessUI();
      } else if (verifyResponse.status === 404 && PublicKeyCredential.signalUnknownCredential) {
        await PublicKeyCredential.signalUnknownCredential({
          rpId, // RP ID must match the one defined on the server
          credentialId: encodedCredential.id
        });
      }
    }
  } catch (err) {
    if (err.name === "NotAllowedError") {
      console.log("User cancelled reauthentication.");
    }
  }
}

document
  .getElementById("reauth-btn")
  .addEventListener("click", triggerButtonReauth);

Резервные стратегии

Резервный вариант обнаружения функции ключей доступа

Всегда устанавливайте webauthn-polyfills и импортируйте его в контекст. Учтите, что если поддерживается PublicKeyCredential, то поддерживается и PublicKeyCredential.getClientCapabilities.

import 'webauthn-polyfills';

Простая резервная сериализация JSON

Всегда устанавливайте webauthn-polyfills и импортируйте его в контекст. Если поддерживается PublicKeyCredential, то поддерживаются и PublicKeyCredential.parseRequestOptionsFromJSON, и PublicKeyCredential.prototype.toJSON.

import 'webauthn-polyfills';