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 dispensar o usuário de digitar um nome de usuário. Isso permite que os usuários se autentiquem selecionando uma conta em 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 a entrada de um nome de usuário. As credenciais que uma chave de segurança pode encontrar sem saber os IDs são chamadas de credenciais detectáveis. A maioria das credenciais FIDO criadas hoje são detectáveis, 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 criadas como chaves de acesso (credenciais detectáveis), especifique residentKey e requireResidentKey quando a credencial for criada.

As partes confiáveis (RPs) podem usar credenciais detectáveis omitindo allowCredentials durante a autenticação de chave de acesso. Nesses casos, o navegador ou 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 da credencial retornado para procurar a conta, em vez de um nome de usuário digitado.

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

requireResidentKey e residentKey

Para criar uma chave de acesso, 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': uma credencial detectável precisa ser criada. Se não for possível criar, NotSupportedError será retornado.
  • 'preferred': o RP prefere criar uma credencial detectável, mas aceita uma credencial não detectável.
  • 'discouraged': o RP prefere criar uma credencial não detectável, mas aceita uma detectável.

requireResidentKey:

  • Essa propriedade é mantida 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 como false.

allowCredentials

As partes restritas podem usar allowCredentials no navigator.credentials.get() para controlar a experiência de autenticação de chaves de acesso. Geralmente, há três tipos de experiência de autenticação de chave de acesso:

Com credenciais detectáveis, os RPs podem mostrar um seletor de conta modal para que o usuário selecione uma conta para fazer login, seguido pela verificação do usuário. Isso é adequado para o fluxo de autenticação da chave de acesso iniciado ao pressionar um botão dedicado à autenticação da chave de acesso.

Para oferecer essa experiência do usuário, omita ou transmita uma matriz vazia ao 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 elas estiverem disponíveis no dispositivo local. Para um usuário que não tem chaves de acesso locais, a caixa de diálogo modal ainda aparece e oferece a opção de apresentar uma chave de outro dispositivo. Durante a transição dos usuários para chaves de acesso, evite essa interface para usuários que não configuraram uma.

Em vez disso, a seleção de uma chave de acesso pode ser incluída 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. Assim, um usuário com chaves de acesso pode "preencher" o formulário de login selecionando a chave de acesso, os usuários com pares de nome de usuário/senha salvos podem selecioná-los e os usuários sem nenhuma delas podem digitar o nome de usuário e a senha.

Essa experiência do usuário é ideal quando o RP está em migração com um uso misto de senhas e chaves de acesso.

Para conseguir 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 username HTML 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 um, ele vai passar pela verificação normal de desbloqueio do dispositivo, e só 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" ...>

Aprenda a criar essa experiência do usuário em Fazer login com uma chave de acesso usando o 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, como ao usar chaves de acesso para reautorização, o identificador do usuário já é conhecido. Nesse caso, gostaríamos de usar uma chave de acesso sem que o navegador ou o SO mostre qualquer forma de seletor de conta. Para fazer isso, transmita uma lista de IDs de credencial no parâmetro allowCredentials.

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

Para oferecer essa experiência, forneça uma lista de IDs de credencial para o usuário que está fazendo login. O RP precisa ser capaz de fazer consultas, porque o usuário já é conhecido. Forneça IDs de credencial como objetos PublicKeyCredentialDescriptor na propriedade 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',
    // 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: geralmente, esse campo é 'public-key'.
  • transports: uma dica de transportes compatíveis com o dispositivo que contém essa credencial, usada pelos navegadores para otimizar a interface que pede ao usuário para apresentar um dispositivo externo. Se for fornecida, essa lista precisa conter o resultado da chamada de 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 para o usuário, permitindo que ele pule a etapa de entrada de nome de usuário. Com a combinação de residentKey, requireResidentKey e allowCredentials, os RPs podem ter experiências de login que:

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

Use credenciais detectáveis com sabedoria. Assim, você cria experiências sofisticadas de login com chave de acesso que os usuários vão achar simples e com mais chances de interagir.