Análise detalhada das credenciais detectáveis

Embora as credenciais do FIDO, como chaves de acesso, tenham como objetivo substituir as senhas, a maioria delas também pode liberar o usuário de digitar um nome de usuário. Isso permite que os usuários façam a autenticação selecionando uma conta de uma lista de chaves de acesso que eles têm para o site atual.

As versões anteriores das chaves de segurança foram projetadas como métodos de autenticação em duas etapas e exigiam os IDs de possíveis credenciais, exigindo, assim, a entrada de um nome de usuário. As credenciais que uma chave de segurança consegue encontrar sem saber os IDs são chamadas de credenciais detectáveis. A maioria das credenciais da FIDO criadas hoje é detectável, principalmente as chaves de acesso armazenadas em um gerenciador de senhas ou em uma chave de segurança moderna.

Para garantir que suas credenciais sejam detectáveis, especifique residentKey e requireResidentKey quando a chave de acesso for criada.

As partes confiáveis (RPs, na sigla em inglês) podem usar credenciais detectáveis ao omitir allowCredentials durante a autenticação da chave de acesso. Nesses casos, o navegador ou o sistema mostra ao usuário uma lista de chaves de acesso disponíveis, identificadas pela propriedade user.name definida no momento da criação. Se o usuário selecionar uma, o valor user.id será incluído na assinatura resultante. O servidor pode usar esse ou o ID de credencial retornado para procurar a conta, em vez de um nome de usuário digitado.

As IUs do seletor de contas, como as discutidas anteriormente, nunca mostram credenciais não detectáveis.

requireResidentKey e residentKey

Para criar uma credencial detectável, especifique authenticatorSelection.residentKey e authenticatorSelection.requireResidentKey em navigator.credentials.create() com os valores indicados da seguinte maneira.

async function register () {
  // ...

  const publicKeyCredentialCreationOptions = {
    // ...
    authenticatorSelection: {
      authenticatorAttachment: 'platform',
      residentKey: 'required',
      requireResidentKey: true,
    }
  };

  const credential = await navigator.credentials.create({
    publicKey: publicKeyCredentialCreationOptions
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;

  // ...
}

residentKey:

  • 'required': é necessário criar uma credencial detectável. Se não for possível criá-la, NotSupportedError será retornado.
  • 'preferred': a RP prefere criar uma credencial detectável, mas aceita uma credencial não detectável.
  • 'discouraged': a RP prefere criar uma credencial não detectável, mas aceita uma credencial detectável.

requireResidentKey:

  • Essa propriedade é retida para compatibilidade com versões anteriores do WebAuthn Nível 1, uma versão mais antiga da especificação. Defina como true se residentKey for 'required'. Caso contrário, defina-o como false.

allowCredentials

Os RPs podem usar allowCredentials em navigator.credentials.get() para controlar a experiência de autenticação da chave de acesso. Geralmente, há três tipos de experiências de autenticação de chaves de acesso:

Com credenciais detectáveis, as partes restritas podem mostrar um seletor de conta modal para o usuário fazer login, seguido da verificação do usuário. Isso é adequado para o fluxo de autenticação de chaves de acesso iniciado ao pressionar um botão dedicado a essa autenticação.

Para proporcionar essa experiência do usuário, omita ou transmita uma matriz vazia para o parâmetro allowCredentials em navigator.credentials.get().

async function authenticate() {
  // ...

  const publicKeyCredentialRequestOptions = {
    // Server generated challenge:
    challenge: ****,
    // The same RP ID as used during registration:
    rpId: 'example.com',
    // You can omit `allowCredentials` as well:
    allowCredentials: []
  };

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

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;
  
  // ...
}

Mostrar o preenchimento automático de um formulário de chave de acesso

O seletor de conta modal descrito acima funciona bem se a maioria dos usuários usar chaves de acesso e disponibilizá-las no dispositivo local. Para um usuário que não tem chaves de acesso locais, a caixa de diálogo modal ainda vai aparecer, oferecendo a opção de apresentar uma chave de acesso de outro dispositivo. Ao fazer a transição dos seus usuários para chaves de acesso, convém evitar essa interface para usuários que não configuraram uma.

Em vez disso, a seleção de uma chave de acesso pode ser dobrada em solicitações de preenchimento automático para os campos em um formulário de login tradicional, junto com nomes de usuário e senhas salvos. Dessa forma, um usuário com chaves de acesso pode "preencher" o formulário de login selecionando a chave de acesso, usuários com pares de nome de usuário/senha salvos podem selecioná-los e usuários que não têm nenhum deles ainda podem digitar o nome de usuário e senha.

Essa experiência do usuário é ideal quando a parte restrita está fazendo uma migração com um uso misto de senhas e chaves de acesso.

Para oferecer essa experiência do usuário, além de transmitir uma matriz vazia para a propriedade allowCredentials ou omitir o parâmetro, especifique mediation: 'conditional' em navigator.credentials.get() e anote um campo de entrada HTML username com autocomplete="username webauthn" ou um campo de entrada password com autocomplete="password webauthn".

A chamada para navigator.credentials.get() não fará com que nenhuma interface seja mostrada, mas, se o usuário focar o campo de entrada com anotação, todas as chaves de acesso disponíveis serão incluídas nas opções de preenchimento automático. Se o usuário selecionar uma, ele passará pela verificação normal de desbloqueio do dispositivo, e somente então a promessa retornada por .get() será resolvida com um resultado. Se o usuário não selecionar uma chave de acesso, a promessa nunca será resolvida.

async function authenticate() {
  // ...

  const publicKeyCredentialRequestOptions = {
    // Server generated challenge:
    challenge: ****,
    // The same RP ID as used during registration:
    rpId: 'example.com',
    // You can omit `allowCredentials` as well:
    allowCredentials: []
  };

  const cred = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions,
    signal: abortController.signal,
    // Specify 'conditional' to activate conditional UI
    mediation: 'conditional'
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;
  
  // ...
}
<input type="text" name="username" autocomplete="username webauthn" ...>

Você pode aprender a criar essa experiência do usuário em Fazer login com uma chave de acesso pelo preenchimento automático de formulários e no codelab Implementar chaves de acesso com o preenchimento automático de formulários em um app da Web.

Reautenticação

Em alguns casos, por exemplo, ao usar chaves de acesso para reautenticação, o identificador do usuário já é conhecido. Nesse caso, queremos usar uma chave de acesso sem que o navegador ou o SO mostre nenhuma forma de seletor de conta. Isso pode ser feito transmitindo uma lista de IDs de credenciais no parâmetro allowCredentials.

Nesse caso, se alguma das credenciais nomeadas estiver disponível localmente, o usuário vai precisar desbloquear o dispositivo imediatamente. Caso contrário, o usuário vai precisar apresentar outro dispositivo (um smartphone ou uma chave de segurança) que tenha uma credencial válida.

Para alcançar essa experiência do usuário, forneça uma lista de IDs de credenciais para o usuário que fez login. A parte restrita poderá consultá-los porque o usuário já é conhecido. Forneça os IDs de credenciais como objetos PublicKeyCredentialDescriptor na propriedade allowCredentials no navigator.credentials.get().

async function authenticate() {
  // ...

  const publicKeyCredentialRequestOptions = {
    // Server generated challenge:
    challenge: ****,
    // The same RP ID as used during registration:
    rpId: 'example.com',
    // Provide a list of PublicKeyCredentialDescriptors:
    allowCredentials: [{
      id: ****,
      type: 'public-key',
      transports: [
        'internal',
        'hybrid'
      ]
    }, {
      id: ****,
      type: 'public-key',
      transports: [
        'internal',
        'hybrid'
      ]
    }, ...]
  };

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

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;
  
  // ...
}

Um objeto PublicKeyCredentialDescriptor consiste em:

  • id: um ID da credencial de chave pública que a RP recebeu no registro da chave de acesso.
  • type: este campo geralmente é 'public-key'.
  • transports: uma dica de transportes compatíveis com o dispositivo com essa credencial, usada por navegadores para otimizar a interface que solicita que o usuário apresente um dispositivo externo. Essa lista, se fornecida, precisa conter o resultado da chamada do getTransports() durante o registro de cada credencial.

Resumo

As credenciais detectáveis tornam a experiência de login com chave de acesso muito mais fácil, permitindo que os usuários pulem a etapa de digitar um nome de usuário. Com a combinação de residentKey, requireResidentKey e allowCredentials, a parte restrita pode ter experiências de login que:

  • Mostrar um seletor de conta modal.
  • Mostrar um preenchimento automático de formulário de chave de acesso.
  • Reautenticação.

Use as credenciais detectáveis com sabedoria. Assim, você pode criar experiências sofisticadas de login com chaves de acesso que vão facilitar o uso e aumentar a probabilidade de os usuários interagirem.