Другое

WebAuthn UserHandle Null: Как исправить ассоциацию PassKey

Узнайте, почему WebAuthn UserHandle равен null при использовании allowCredentials и как надежно связывать пользователей с PassKey на всех устройствах, включая Samsung. Полное руководство по реализации с примерами кода.

Почему UserHandle равен null в WebAuthn/PassKey Attestation при использовании allowCredentials, и как можно надежно связывать пользователей с их PassKeys?

Я реализую аутентификацию PassKey и столкнулся с проблемой, когда UserHandle постоянно равен null, когда я указываю ‘allowCredentials’ в вызове credentials.get(). Когда я убираю allowCredentials, UserHandle заполняется правильно.

Текущая реализация:

javascript
var getAssertionOptions = {
    timeout: 50000,
    challenge: stringToArrayBuffer(serverChallenge.Token),
    allowCredentials : allowCredentials
};

В моем коде верификации:

javascript
if (
  assertion == null ||
  assertion.Id == null ||
  assertion.AuthenticatorData == null ||
  assertion.ClientDataJSON == null ||
  assertion.Signature == null
) {
  // assertion.UserHandle равен null для Samsung телефона
  return FAIL_STATUS;
}

Основные вопросы:

  1. Является ли UserHandle постоянно равным null для Samsung телефонов, даже когда requireResidentKey установлен в true при создании учетных данных?

  2. Как можно надежно связывать пользователей сайта с их PassKeys в моей базе данных, если UserHandle равен null? Следует ли использовать ID Assertion вместо этого?

  3. Использование ID Assertion для связи с пользователем помешает ли работоспособным discoverable credentials на устройствах Samsung?

  4. Является ли эта проблема с null UserHandle специфичной для navigator.credentials.get() или она также затрагивает navigator.credentials.create()?

  5. Может ли это быть связано с ограничениями Android в отношении discoverable credentials на security keys?

Конфигурация создания PassKey:

javascript
var createCredentialOptions = {
  rp: { name: "WebAuthn Sample App", icon: "" },
  user: {
    id: stringToArrayBuffer("some.user.guid"),
    name: "maherrj@gmail.com",
    displayName: "Richard Maher",
    icon: ""
  },
  pubKeyCredParams: [
    { type: "public-key", alg: -7 },
    { type: "public-key", alg: -257 }
  ],
  authenticatorSelection: {
    requireResidentKey: true,
    userVerification: "discouraged",
    authenticatorAttachment: "cross-platform"
  },
  timeout: 30000,
  challenge: stringToArrayBuffer(serverChallenge.Token),
  excludeCredentials: [],
  attestation: "none"
};

Обработка Assertion:

javascript
return navigator.credentials
  .get({ publicKey: getAssertionOptions })
  .then((rawAssertion) => {
    var assertion = {
      id: base64encode(rawAssertion.rawId),
      clientDataJSON: arrayBufferToString(rawAssertion.response.clientDataJSON),
      userHandle: base64encode(rawAssertion.response.userHandle),
      signature: base64encode(rawAssertion.response.signature),
      authenticatorData: base64encode(rawAssertion.response.authenticatorData)
    };

Мне нужна помощь в решении проблемы с null UserHandle и обеспечении надежной связи пользователей с PassKeys на разных устройствах, особенно на Samsung телефонах.

WebAuthn: Почему UserHandle равен null при использовании allowCredentials

UserHandle в WebAuthn постоянно равен null при использовании allowCredentials, поскольку спецификация требует такого поведения для обеспечения конфиденциальности пользователей и предотвращения отслеживания между сайтами. Когда вы указываете allowCredentials, вы запрашиваете аутентификацию от конкретных известных учетных данных, и браузер/платформа намеренно опускает UserHandle, чтобы предотвратить корреляцию учетных данных между разными сервисами. Для надежной ассоциации пользователей с PassKey следует использовать идентификатор учетных данных (rawId) в качестве основного идентификатора, так как он уникально идентифицирует каждые учетные данные, сохраняя при этом конфиденциальность пользователей.

Содержание

Поведение null для UserHandle

Поведение null для UserHandle при использовании allowCredentials является намеренным и соответствует спецификации. Согласно спецификации WebAuthn, UserHandle опускается, когда:

  1. Используется параметр allowCredentials
  2. Аутентификатор не хранит информацию о пользователе для запрошенных учетных данных
  3. Конфиденциальность не позволяет раскрывать информацию о пользователе

“Идентификатор пользователя - это буфер байтов, который relying party может использовать для запоминания учетной записи пользователя, но он не предназначен для использования в качестве первичного ключа для учетной записи.” - Спецификация WebAuthn уровня 2

Этот выбор дизайна предотвращает для веб-сайтов:

  • Корреляцию идентичности пользователя между разными сервисами
  • Отслеживание пользователей на основе информации об учетных данных
  • Создание профилей пользователей без явного согласия

Когда вы удаляете allowCredentials и запрашиваете “обнаруживаемые учетные данные” (ранее известные как резидентные ключи), UserHandle может быть заполнен, поскольку аутентификатор может предоставить информацию о пользователе, не раскрывая, какие именно учетные данные используются.

Особенности устройств Samsung

Устройства Samsung Galaxy с поддержкой PassKey часто демонстрируют более агрессивное поведение в отношении защиты конфиденциальности:

  1. Настройки конфиденциальности по умолчанию: Реализация Samsung отдает приоритет конфиденциальности пользователей, часто обнуляя UserHandle даже при создании резидентных ключей
  2. Ограничения платформы: Реализация WebAuthn в Android имеет различия между производителями, что влияет на обработку учетных данных
  3. Поведение ключей безопасности: При использовании Samsung Pass в качестве ключа безопасности поведение может отличаться от платформенных аутентификаторов

На основе распространенных шаблонов реализации:

  • Устройства Samsung часто возвращают null для UserHandle независимо от настройки requireResidentKey
  • Это более выражено, когда указан allowCredentials
  • Поведение согласовано во всей серии Samsung Galaxy S, Note и складных устройствах

Рекомендация по тестированию: Тестируйте на нескольких моделях устройств Samsung и версиях Android, чтобы установить шаблон, поскольку поведение может различаться в реализациях Android 12, 13 и 14.

Стратегии надежной ассоциации пользователей

Когда UserHandle равен null, вам нужны альтернативные стратегии для ассоциации пользователей:

Основная стратегия: Использование идентификатора учетных данных (rawId)

Идентификатор учетных данных (rawId или id в вашем утверждении) является наиболее надежным идентификатором:

javascript
// Сохраняйте это при создании учетных данных
const credentialId = base64encode(rawCredential.rawId);

// Используйте это при аутентификации для поиска пользователя
const assertionId = base64encode(rawAssertion.rawId);

Пример схемы базы данных:

sql
CREATE TABLE user_credentials (
    id UUID PRIMARY KEY,
    user_id UUID NOT NULL,
    credential_id VARCHAR(255) NOT NULL UNIQUE,
    public_key JSONB,
    created_at TIMESTAMP,
    last_used_at TIMESTAMP
);

Вторичная стратегия: Ассоциация на стороне клиента

Для усиления безопасности реализуйте ассоциацию на стороне клиента:

javascript
// При создании учетных данных
const createCredentialOptions = {
    // ... существующие параметры
    user: {
        id: stringToArrayBuffer(userId), // ID пользователя в вашей системе
        name: userEmail,
        displayName: userDisplayName
    }
};

// Сохраняйте сопоставление: credentialId -> userId

Запасная стратегия: Сопоставление “запрос-ответ”

Для крайних случаев реализуйте временную ассоциацию на основе запроса:

javascript
// Временно сохраняйте сопоставление запроса -> userId
const challengeStore = new Map();

// При утверждении
const userId = challengeStore.get(challenge);
if (userId) {
    // Ассоциируйте утверждение с этим пользователем
}

Влияние на обнаруживаемые учетные данные

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

  1. Обнаруживаемые учетные данные все равно работают - пользователь все равно может выбрать, какие учетные данные использовать
  2. Идентификатор учетных данных остается стабильным - он уникально идентифицирует каждые учетные данные независимо от UserHandle
  3. Поток аутентификации остается неизменным - процесс проверки подписи работает идентично

Ключевые моменты:

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

Тестирование обнаруживаемых учетных данных:

javascript
// Тестируйте оба сценария
const optionsWithAllowCredentials = {
    allowCredentials: [{ id: knownCredentialId }]
};

const optionsWithoutAllowCredentials = {
    // Запрашиваем обнаруживаемые учетные данные
};

Операции Create vs Get

Поведение UserHandle различается между операциями credentials.create() и credentials.get():

Операция credentials.create()

  • UserHandle обычно заполняется при создании резидентных ключей
  • requireResidentKey: true увеличивает вероятность наличия UserHandle
  • Устройства Samsung все равно могут возвращать null для UserHandle при создании

Операция credentials.get()

  • UserHandle равен null, когда указан allowCredentials
  • UserHandle может быть доступен, когда запрашиваются обнаруживаемые учетные данные
  • Поведение более согласовано между устройствами при операциях get

Важное замечание: UserHandle, возвращаемый при операциях create, не гарантированно будет таким же, как то, что может быть возвращено при операциях get, даже для одних и тех же учетных данных.

Лучшие практики реализации

1. Всегда используйте идентификатор учетных данных в качестве основного идентификатора

javascript
// Обработка утверждения
function processAssertion(rawAssertion) {
    return {
        id: base64encode(rawAssertion.rawId), // Основной идентификатор
        clientDataJSON: arrayBufferToString(rawAssertion.response.clientDataJSON),
        userHandle: base64encode(rawAssertion.response.userHandle || ''), // Запасной вариант - пустая строка
        signature: base64encode(rawAssertion.response.signature),
        authenticatorData: base64encode(rawAssertion.response.authenticatorData),
        userId: null // Будет заполнен при поиске учетных данных
    };
}

2. Реализуйте плавные запасные варианты

javascript
// Обрабатывайте оба сценария
async function authenticateUser(challenge, allowCredentials = null) {
    const options = {
        challenge: challenge,
        timeout: 60000
    };
    
    if (allowCredentials) {
        options.allowCredentials = allowCredentials;
    }
    
    try {
        const assertion = await navigator.credentials.get({ publicKey: options });
        return processAssertion(assertion);
    } catch (err) {
        console.error('Аутентификация не удалась:', err);
        throw err;
    }
}

3. Проектирование базы данных для нескольких учетных данных

javascript
// Поддержка нескольких учетных данных на пользователя
const userCredentialSchema = {
    userId: String,
    credentials: [{
        credentialId: String,
        publicKey: String,
        counter: Number,
        deviceInfo: {
            type: String,
            authenticatorAttachment: String,
            aaguid: String
        },
        createdAt: Date,
        lastUsedAt: Date
    }]
};

Устранение распространенных проблем

Проблема: UserHandle равен null на всех устройствах

Возможные причины:

  • Указан параметр allowCredentials
  • Резидентный ключ не был создан правильно
  • Настройки конфиденциальности в браузере

Решения:

  • Сначала протестируйте без allowCredentials
  • Проверьте параметры создания резидентного ключа
  • Проверьте настройки конфиденциальности в браузере

Проблема: Идентификатор учетных данных не согласован

Возможные причины:

  • Разные аутентификаторы возвращают разные форматы ID
  • Проблемы с кодированием данных

Решения:

  • Стандартизируйте кодирование (всегда используйте base64url)
  • Сохраняйте rawId как ArrayBuffer в базе данных
  • Реализуйте утилиты правильного кодирования/декодирования

Проблема: Сбои, специфичные для устройств Samsung

Возможные причины:

  • Агрессивная реализация конфиденциальности Samsung
  • Различия в версиях Android
  • Проблемы с конфигурацией Samsung Pass

Решения:

  • Тестируйте на нескольких устройствах Samsung
  • Реализуйте специфичную для устройств обработку, если необходимо
  • Рассмотрите запасные методы аутентификации

Источники

  1. Спецификация WebAuthn уровня 2 - Идентификатор пользователя
  2. Руководство по реализации WebAuthn - Mozilla Developer Network
  3. Лучшие практики Android WebAuthn - Google Developers
  4. Документация разработчика Samsung Pass
  5. Обзор WebAuthn от FIDO Alliance

Заключение

Поведение null для UserHandle при использовании allowCredentials является преднамеренной функцией конфиденциальности спецификации WebAuthn, а не ошибкой. Чтобы обеспечить надежную ассоциацию пользователей на всех устройствах, особенно на телефонах Samsung:

  1. Используйте идентификатор учетных данных (rawId) в качестве основного идентификатора - он стабильный, уникальный и доступен независимо от статуса UserHandle
  2. Проектируйте свою базу данных для обработки нескольких учетных данных на пользователя - это позволяет учитывать сценарии как с allowCredentials, так и с обнаруживаемыми учетными данными
  3. Реализуйте плавные запасные варианты - обрабатывайте как сценарии с null, так и с заполненным UserHandle
  4. Тестируйте на различных типах устройств - особенно на устройствах Samsung, которые имеют более агрессивное поведение в отношении конфиденциальности
  5. Рассмотрите ассоциацию на стороне клиента - при возможности безопасно храните сопоставления пользователей на стороне клиента

Следуя этим стратегиям, вы можете создать надежную систему аутентификации PassKey, которая работает согласованно на всех устройствах, сохраняя при этом конфиденциальность и безопасность пользователей. Помните, что идентификатор учетных данных остается наиболее надежным идентификатором для ассоциации пользователей в реализациях WebAuthn.

Авторы
Проверено модерацией
Модерация