Criar seu primeiro app WebAuthn

1. Antes de começar

A API Web Authentication, também conhecida como WebAuthn, permite criar e usar credenciais de chave pública com escopo de origem para autenticar usuários.

Ela tem suporte ao uso de autenticadores U2F ou FIDO2 de BLE, NFC e roaming USB, também conhecidos como chaves de segurança, além de um autenticador de plataforma, que permite que os usuários façam a autenticação com impressão digital ou bloqueio de tela.

Neste codelab, você vai criar um site com uma funcionalidade simples de reautenticação que usa um sensor de impressão digital. A reautenticação protege os dados da conta porque exige que os usuários que já fizeram login em um site façam a autenticação novamente ao tentar entrar em seções importantes ou revisitar o site após um determinado período.

Pré-requisitos

  • Conhecimentos básicos de como a WebAuthn funciona.
  • Habilidades básicas de programação com JavaScript.

O que você vai fazer

  • Criar um site com uma funcionalidade simples de reautenticação que usa um sensor de impressão digital.

O que será necessário

  • Um dos seguintes dispositivos:
    • Um dispositivo Android, de preferência com um sensor biométrico
    • Um iPhone ou iPad com iOS 14 ou versão mais recente e Touch ID ou Face ID
    • Um MacBook Pro ou Air com macOS Big Sur ou versão mais recente e Touch ID
    • Windows 10 19H1 ou versão mais recente com o Windows Hello configurado
  • Um dos seguintes navegadores:
    • Google Chrome 67 ou versão mais recente
    • Microsoft Edge 85 ou versão mais recente
    • Safari 14 ou versão mais recente

2. Começar a configuração

Neste codelab, você usa um serviço chamado glitch (link em inglês). Nele, é possível editar o código do lado do cliente e do servidor com JavaScript e fazer a implantação instantânea.

Acesse https://glitch.com/edit/#!/webauthn-codelab-start (link em inglês).

Veja como funciona

Siga estas etapas para ver o estado inicial do site:

  1. Clique em 62bb7a6aac381af8.png Show > 3343769d04c09851.png In a New Window para ver o site ativo (link em inglês).
  2. Digite um nome de usuário de sua preferência e clique em Next.
  3. Digite uma senha e clique em Sign-in.

A senha é ignorada, mas a autenticação ainda será feita e você vai acessar a página inicial.

  1. Clique em Try reauth e repita a segunda, terceira e quarta etapas.
  2. Clique em Sign out.

Será necessário digitar a senha sempre que você tentar fazer login. Isso emula um usuário que precisa fazer a autenticação novamente antes de poder acessar uma seção importante de um site.

Remixar o código

  1. Acesse a página em inglês WebAuthn / FIDO2 API Codelab.
  2. Clique no nome do seu projeto > Remix Project 306122647ce93305.png para bifurcar o projeto e continuar com sua própria versão em um novo URL.

8d42bd24f0fd185c.png

3. Registrar uma credencial com uma impressão digital

Você precisa registrar uma credencial gerada por um UVPA, um autenticador integrado ao dispositivo e que verifica a identidade do usuário. Isso geralmente é detectado como um sensor de impressão digital, dependendo do dispositivo do usuário.

Adicione esse recurso à página /home:

260aab9f1a2587a7.png

Criar uma função registerCredential()

Crie uma função registerCredential(), que registra uma nova credencial.

public/client.js (link em inglês)

export const registerCredential = async () => {

};

Acessar o desafio e outras opções do endpoint do servidor

Antes de pedir ao usuário para registrar uma nova credencial, é necessário solicitar que o servidor retorne parâmetros para transmitir a WebAuthn, incluindo um desafio. Felizmente, você já tem um endpoint de servidor que responde com esses parâmetros.

Adicione o seguinte código a registerCredential():

public/client.js (link em inglês)

const opts = {
  attestation: 'none',
  authenticatorSelection: {
    authenticatorAttachment: 'platform',
    userVerification: 'required',
    requireResidentKey: false
  }
};

const options = await _fetch('/auth/registerRequest', opts);

O protocolo entre um servidor e um cliente não faz parte da especificação WebAuthn. No entanto, este codelab foi projetado para se alinhar à especificação WebAuthn, e o objeto JSON que você transmite ao servidor é muito semelhante ao PublicKeyCredentialCreationOptions (link em inglês), o que é bem intuitivo para você. A tabela a seguir contém os parâmetros importantes que você pode transmitir ao servidor e explica o que eles fazem:

Parâmetros

Descrições

attestation

Preferência para a transmissão de atestado: none, indirect ou direct. Escolha none, a menos que você precise de um.

excludeCredentials

Matriz de PublicKeyCredentialDescriptor (link em inglês) para que o autenticador possa evitar a criação de cópias.

authenticatorSelection

authenticatorAttachment

Filtre os autenticadores disponíveis. Se você quiser que um autenticador seja conectado ao dispositivo, use platform. Para autenticadores de roaming, use cross-platform.

userVerification

Determine se a verificação de usuário local do autenticador é required, preferred ou discouraged. Se você quiser uma autenticação por impressão digital ou bloqueio de tela, use required.

requireResidentKey

Use true se quiser que a credencial criada fique disponível para mudanças de UX futuras relacionadas ao seletor de contas.

Para saber mais sobre essas opções, consulte a seção 5.4. Options for Credential Creation (dictionary PublicKeyCredentialCreationOptions) (link em inglês).

Veja a seguir exemplos de opções recebidas do servidor.

{
  "rp": {
    "name": "WebAuthn Codelab",
    "id": "webauthn-codelab.glitch.me"
  },
  "user": {
    "displayName": "User Name",
    "id": "...",
    "name": "test"
  },
  "challenge": "...",
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    }, {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [
    {
      "id": "...",
      "type": "public-key",
      "transports": [
        "internal"
      ]
    }
  ],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "userVerification": "required"
  }
}

Criar uma credencial

  1. Como essas opções são entregues codificadas para passar pelo protocolo HTTP, converta alguns parâmetros de volta para um binário, especificamente, user.id, challenge e instâncias de id incluídas na matriz excludeCredentials:

public/client.js (link em inglês)

options.user.id = base64url.decode(options.user.id);
options.challenge = base64url.decode(options.challenge);

if (options.excludeCredentials) {
  for (let cred of options.excludeCredentials) {
    cred.id = base64url.decode(cred.id);
  }
}
  1. Chame o método navigator.credentials.create() para criar uma nova credencial.

Com essa chamada, o navegador interage com o autenticador e tenta verificar a identidade do usuário com o UVPA.

public/client.js (link em inglês)

const cred = await navigator.credentials.create({
  publicKey: options,
});

Depois que o usuário verificar a identidade, você vai receber um objeto de credencial para enviar ao servidor e registrar o autenticador.

Registrar a credencial no endpoint do servidor

Veja um exemplo de objeto de credencial que você deve ter recebido.

{
  "id": "...",
  "rawId": "...",
  "type": "public-key",
  "response": {
    "clientDataJSON": "...",
    "attestationObject": "..."
  }
}
  1. Assim como quando você recebeu um objeto de opção para registrar uma credencial, codifique os parâmetros binários dela para que sejam entregues ao servidor como uma string:

public/client.js (link em inglês)

const credential = {};
credential.id = cred.id;
credential.rawId = base64url.encode(cred.rawId);
credential.type = cred.type;

if (cred.response) {
  const clientDataJSON =
    base64url.encode(cred.response.clientDataJSON);
  const attestationObject =
    base64url.encode(cred.response.attestationObject);
  credential.response = {
    clientDataJSON,
    attestationObject,
  };
}
  1. Armazene o ID da credencial localmente para que ele possa ser usado na autenticação quando o usuário voltar:

public/client.js (link em inglês)

localStorage.setItem(`credId`, credential.id);
  1. Envie o objeto para o servidor e, se ele retornar HTTP code 200, considere a nova credencial como registrada.

public/client.js (link em inglês)

return await _fetch('/auth/registerResponse' , credential);

Agora você tem a função registerCredential() completa.

Código final desta seção

public/client.js (link em inglês)

...
export const registerCredential = async () => {
  const opts = {
    attestation: 'none',
    authenticatorSelection: {
      authenticatorAttachment: 'platform',
      userVerification: 'required',
      requireResidentKey: false
    }
  };

  const options = await _fetch('/auth/registerRequest', opts);

  options.user.id = base64url.decode(options.user.id);
  options.challenge = base64url.decode(options.challenge);

  if (options.excludeCredentials) {
    for (let cred of options.excludeCredentials) {
      cred.id = base64url.decode(cred.id);
    }
  }

  const cred = await navigator.credentials.create({
    publicKey: options
  });

  const credential = {};
  credential.id =     cred.id;
  credential.rawId =  base64url.encode(cred.rawId);
  credential.type =   cred.type;

  if (cred.response) {
    const clientDataJSON =
      base64url.encode(cred.response.clientDataJSON);
    const attestationObject =
      base64url.encode(cred.response.attestationObject);
    credential.response = {
      clientDataJSON,
      attestationObject
    };
  }

  localStorage.setItem(`credId`, credential.id);

  return await _fetch('/auth/registerResponse' , credential);
};
...

4. Criar a IU para registrar, receber e remover credenciais

É recomendável ter uma lista de credenciais registradas e botões para que elas possam ser removidas.

9b5b5ae4a7b316bd.png

Criar um marcador de posição de IU

Adicione a IU para listar as credenciais e um botão com a função de registrar uma nova credencial. Dependendo da disponibilidade do recurso, você vai remover a classe hidden da mensagem de aviso ou do botão para registrar uma nova credencial. O ul#list é o marcador de posição para adicionar uma lista de credenciais registradas.

views/home.html (link em inglês)

<p id="uvpa_unavailable" class="hidden">
  This device does not support User Verifying Platform Authenticator. You can't register a credential.
</p>
<h3 class="mdc-typography mdc-typography--headline6">
  Your registered credentials:
</h3>
<section>
  <div id="list"></div>
</section>
<mwc-button id="register" class="hidden" icon="fingerprint" raised>Add a credential</mwc-button>

Detecção de recursos e disponibilidade do UVPA

Siga estas etapas para verificar a disponibilidade do UVPA:

  1. Analise window.PublicKeyCredential para verificar se a WebAuthn está disponível.
  2. Chame PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() para verificar se há UVPAs disponíveis. Se estiverem, você verá o botão para registrar uma nova credencial. Se nenhum deles estiver disponível, você precisará mostrar a mensagem de aviso.

views/home.html (link em inglês)

const register = document.querySelector('#register');

if (window.PublicKeyCredential) {
  PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
  .then(uvpaa => {
    if (uvpaa) {
      register.classList.remove('hidden');
    } else {
      document
        .querySelector('#uvpa_unavailable')
        .classList.remove('hidden');
    }
  });
} else {
  document
    .querySelector('#uvpa_unavailable')
    .classList.remove('hidden');
}

Receber e mostrar uma lista de credenciais

  1. Crie uma função getCredentials() para receber as credenciais registradas e mostrar em uma lista. Felizmente, você já tem um endpoint bem útil no servidor /auth/getKeys, em que é possível buscar credenciais registradas para o usuário conectado.

O JSON retornado inclui informações de credencial, como id e publicKey. Você pode criar um HTML para que elas sejam mostradas ao usuário.

views/home.html (link em inglês)

const getCredentials = async () => {
  const res = await _fetch('/auth/getKeys');
  const list = document.querySelector('#list');
  const creds = html`${res.credentials.length > 0 ? res.credentials.map(cred => html`
    <div class="mdc-card credential">
      <span class="mdc-typography mdc-typography--body2">${cred.credId}</span>
      <pre class="public-key">${cred.publicKey}</pre>
      <div class="mdc-card__actions">
        <mwc-button id="${cred.credId}" @click="${removeCredential}" raised>Remove</mwc-button>
      </div>
    </div>`) : html`
    <p>No credentials found.</p>
    `}`;
  render(creds, list);
};
  1. Invoque getCredentials() para mostrar as credenciais disponíveis assim que o usuário acessar a página /home.

views/home.html (link em inglês)

getCredentials();

Remover a credencial

Na lista de credenciais, você adicionou um botão para remover cada uma delas. Você pode enviar uma solicitação a /auth/removeKey com o parâmetro de consulta credId para fazer a remoção.

public/client.js (link em inglês)

export const unregisterCredential = async (credId) => {
  localStorage.removeItem('credId');
  return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
  1. Anexe unregisterCredential à instrução import já existente.

views/home.html (link em inglês)

import { _fetch, unregisterCredential } from '/client.js';
  1. Adicione uma função para chamar quando o usuário clicar em Remove.

views/home.html (link em inglês)

const removeCredential = async e => {
  try {
    await unregisterCredential(e.target.id);
    getCredentials();
  } catch (e) {
    alert(e);
  }
};

Registrar uma credencial

Você pode chamar registerCredential() para registrar uma nova credencial quando o usuário clicar em Add a credential.

  1. Anexe registerCredential à instrução import já existente.

views/home.html (link em inglês)

import { _fetch, registerCredential, unregisterCredential } from '/client.js';
  1. Invoque registerCredential() com opções para navigator.credentials.create().

Não se esqueça de renovar a lista de credenciais chamando getCredentials() após o registro.

views/home.html (link em inglês)

register.addEventListener('click', e => {
  registerCredential().then(user => {
    getCredentials();
  }).catch(e => alert(e));
});

Agora, será possível registrar uma nova credencial e ver informações sobre ela. Você pode testar isso no seu site ativo.

Código final desta seção

views/home.html (link em inglês)

...
      <p id="uvpa_unavailable" class="hidden">
        This device does not support User Verifying Platform Authenticator. You can't register a credential.
      </p>
      <h3 class="mdc-typography mdc-typography--headline6">
        Your registered credentials:
      </h3>
      <section>
        <div id="list"></div>
        <mwc-fab id="register" class="hidden" icon="add"></mwc-fab>
      </section>
      <mwc-button raised><a href="/reauth">Try reauth</a></mwc-button>
      <mwc-button><a href="/auth/signout">Sign out</a></mwc-button>
    </main>
    <script type="module">
      import { _fetch, registerCredential, unregisterCredential } from '/client.js';
      import { html, render } from 'https://unpkg.com/[email protected]/lit-html.js?module';

      const register = document.querySelector('#register');

      if (window.PublicKeyCredential) {
        PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
        .then(uvpaa => {
          if (uvpaa) {
            register.classList.remove('hidden');
          } else {
            document
              .querySelector('#uvpa_unavailable')
              .classList.remove('hidden');
          }
        });
      } else {
        document
          .querySelector('#uvpa_unavailable')
          .classList.remove('hidden');
      }

      const getCredentials = async () => {
        const res = await _fetch('/auth/getKeys');
        const list = document.querySelector('#list');
        const creds = html`${res.credentials.length > 0 ? res.credentials.map(cred => html`
          <div class="mdc-card credential">
            <span class="mdc-typography mdc-typography--body2">${cred.credId}</span>
            <pre class="public-key">${cred.publicKey}</pre>
            <div class="mdc-card__actions">
              <mwc-button id="${cred.credId}" @click="${removeCredential}" raised>Remove</mwc-button>
            </div>
          </div>`) : html`
          <p>No credentials found.</p>
          `}`;
        render(creds, list);
      };

      getCredentials();

      const removeCredential = async e => {
        try {
          await unregisterCredential(e.target.id);
          getCredentials();
        } catch (e) {
          alert(e);
        }
      };

      register.addEventListener('click', e => {
        registerCredential({
          attestation: 'none',
          authenticatorSelection: {
            authenticatorAttachment: 'platform',
            userVerification: 'required',
            requireResidentKey: false
          }
        })
        .then(user => {
          getCredentials();
        })
        .catch(e => alert(e));
      });
    </script>
...

public/client.js (link em inglês)

...
export const unregisterCredential = async (credId) => {
  localStorage.removeItem('credId');
  return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
...

5. Autenticar o usuário com uma impressão digital

Você já tem uma credencial registrada e pronta para usar como forma de autenticação do usuário. Agora, você vai adicionar ao site a funcionalidade de reautenticação. Esta é a experiência do usuário:

Quando o usuário acessa a página /reauth, ele vê um botão Authenticate, caso a autenticação biométrica esteja disponível. A autenticação com uma impressão digital (UVPA) é iniciada quando o usuário toca em Authenticate, faz a autenticação e é direcionado para a página /home. Se a autenticação biométrica não estiver disponível ou falhar, a IU vai voltar a usar o formulário de senha já existente.

b8770c4e7475b075.png

Criar a função authenticate()

Crie uma função chamada authenticate(), que verifica a identidade do usuário com uma impressão digital. O código JavaScript será adicionado aqui:

public/client.js (link em inglês)

export const authenticate = async () => {

};

Acessar o desafio e outras opções do endpoint do servidor

  1. Antes da autenticação, veja se o usuário tem um ID de credencial armazenado e o defina como um parâmetro de consulta.

Quando você fornece um ID de credencial junto a outras opções, o servidor pode fornecer a allowCredentials relevante, o que torna a verificação de usuários confiável.

public/client.js (link em inglês)

const opts = {};

let url = '/auth/signinRequest';
const credId = localStorage.getItem(`credId`);
if (credId) {
  url  = `?credId=${encodeURIComponent(credId)}`;
}
  1. Antes de pedir ao usuário para autenticar, peça para o servidor enviar um desafio e outros parâmetros. Chame _fetch() com opts como um argumento para enviar uma solicitação POST ao servidor.

public/client.js (link em inglês)

const options = await _fetch(url, opts);

Veja alguns exemplos de opções que você deve receber (alinhadas a PublicKeyCredentialRequestOptions, link em inglês).

{
  "challenge": "...",
  "timeout": 1800000,
  "rpId": "webauthn-codelab.glitch.me",
  "userVerification": "required",
  "allowCredentials": [
    {
      "id": "...",
      "type": "public-key",
      "transports": [
        "internal"
      ]
    }
  ]
}

A opção mais importante aqui é allowCredentials. Quando você recebe opções do servidor, allowCredentials precisa ser um único objeto em uma matriz (se uma credencial com o ID no parâmetro de consulta for encontrada no lado do servidor) ou uma matriz vazia (se não for possível encontrar essa credencial).

  1. Resolva a promessa com null quando allowCredentials for uma matriz vazia de modo que a IU retorne para solicitar uma senha.
if (options.allowCredentials.length === 0) {
  console.info('No registered credentials found.');
  return Promise.resolve(null);
}

Verificar o usuário localmente e receber uma credencial

  1. Como essas opções são entregues codificadas para passar pelo protocolo HTTP, converta alguns parâmetros de volta para binários, especificamente challenge e instâncias de id incluídas na matriz allowCredentials:

public/client.js (link em inglês)

options.challenge = base64url.decode(options.challenge);

for (let cred of options.allowCredentials) {
  cred.id = base64url.decode(cred.id);
}
  1. Chame o método navigator.credentials.get() para verificar a identidade do usuário com um UVPA.

public/client.js (link em inglês)

const cred = await navigator.credentials.get({
  publicKey: options
});

Depois que o usuário confirmar a identidade, você vai receber um objeto de credencial para enviar ao servidor e autenticar o usuário.

Verificar a credencial

Veja um exemplo de objeto PublicKeyCredential (response é AuthenticatorAssertionResponse, links em inglês) que você deve ter recebido:

{
  "id": "...",
  "type": "public-key",
  "rawId": "...",
  "response": {
    "clientDataJSON": "...",
    "authenticatorData": "...",
    "signature": "...",
    "userHandle": ""
  }
}
  1. Codifique os parâmetros binários da credencial para que ela possa ser entregue ao servidor como uma string:

public/client.js (link em inglês)

const credential = {};
credential.id = cred.id;
credential.type = cred.type;
credential.rawId = base64url.encode(cred.rawId);

if (cred.response) {
  const clientDataJSON =
    base64url.encode(cred.response.clientDataJSON);
  const authenticatorData =
    base64url.encode(cred.response.authenticatorData);
  const signature =
    base64url.encode(cred.response.signature);
  const userHandle =
    base64url.encode(cred.response.userHandle);
  credential.response = {
    clientDataJSON,
    authenticatorData,
    signature,
    userHandle,
  };
}
  1. Envie o objeto para o servidor e, se ele retornar HTTP code 200, considere o usuário como conectado:

public/client.js (link em inglês)

return await _fetch(`/auth/signinResponse`, credential);

Agora você tem a função authentication() completa.

Código final desta seção

public/client.js (link em inglês)

...
export const authenticate = async () => {
  const opts = {};

  let url = '/auth/signinRequest';
  const credId = localStorage.getItem(`credId`);
  if (credId) {
    url  = `?credId=${encodeURIComponent(credId)}`;
  }

  const options = await _fetch(url, opts);

  if (options.allowCredentials.length === 0) {
    console.info('No registered credentials found.');
    return Promise.resolve(null);
  }

  options.challenge = base64url.decode(options.challenge);

  for (let cred of options.allowCredentials) {
    cred.id = base64url.decode(cred.id);
  }

  const cred = await navigator.credentials.get({
    publicKey: options
  });

  const credential = {};
  credential.id = cred.id;
  credential.type = cred.type;
  credential.rawId = base64url.encode(cred.rawId);

  if (cred.response) {
    const clientDataJSON =
      base64url.encode(cred.response.clientDataJSON);
    const authenticatorData =
      base64url.encode(cred.response.authenticatorData);
    const signature =
      base64url.encode(cred.response.signature);
    const userHandle =
      base64url.encode(cred.response.userHandle);
    credential.response = {
      clientDataJSON,
      authenticatorData,
      signature,
      userHandle,
    };
  }

  return await _fetch(`/auth/signinResponse`, credential);
};
...

6. Ativar a experiência de reautenticação

Criar a IU

Quando o usuário voltar, você vai precisar fazer a reautenticação da forma mais fácil e segura possível. É aqui que a autenticação biométrica se destaca. No entanto, existem casos em que a autenticação biométrica pode não funcionar:

  • O UVPA não está disponível.
  • O usuário ainda não registrou nenhuma credencial no dispositivo.
  • Foi feita a limpeza do armazenamento, e o dispositivo não se lembra mais do ID da credencial.
  • O usuário não consegue verificar a identidade por algum motivo, como quando o dedo está molhado ou ele está usando máscara.

Por isso, é sempre importante oferecer outras opções de login como substitutas. Neste codelab, você vai usar a solução de senha baseada em formulário.

19da999b0145054.png

  1. Adicione a IU para mostrar um botão de autenticação que invoca a autenticação biométrica, além do formulário de senha.

Use a classe hidden para mostrar e ocultar seletivamente uma delas, dependendo do estado do usuário.

views/reauth.html (link em inglês)

<div id="uvpa_available" class="hidden">
  <h2>
    Verify your identity
  </h2>
  <div>
    <mwc-button id="reauth" raised>Authenticate</mwc-button>
  </div>
  <div>
    <mwc-button id="cancel">Sign-in with password</mwc-button>
  </div>
</div>
  1. Anexe class="hidden" ao formulário:

views/reauth.html (link em inglês)

<form id="form" method="POST" action="/auth/password" class="hidden">

Detecção de recursos e disponibilidade do UVPA

Os usuários vão precisar fazer login com uma senha se uma destas condições for atendida:

  • A WebAuthn não está disponível.
  • O UVPA não está disponível.
  • Um ID de credencial para esse UVPA não foi detectado.

Mostre ou oculte o botão de autenticação de acordo com a seleção:

views/reauth.html (link em inglês)

if (window.PublicKeyCredential) {
  PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
  .then(uvpaa => {
    if (uvpaa && localStorage.getItem(`credId`)) {
      document
        .querySelector('#uvpa_available')
        .classList.remove('hidden');
    } else {
      form.classList.remove('hidden');
    }
  });
} else {
  form.classList.remove('hidden');
}

Usar o formulário de senha

É necessário dar ao usuário a opção de fazer login com uma senha.

Mostre o formulário de senha e oculte o botão de autenticação quando o usuário clicar em Sign in with password:

views/reauth.html (link em inglês)

const cancel = document.querySelector('#cancel');
cancel.addEventListener('click', e => {
  form.classList.remove('hidden');
  document
    .querySelector('#uvpa_available')
    .classList.add('hidden');
});

c4a82800889f078c.png

Invocar a autenticação biométrica

Por fim, ative a autenticação biométrica.

  1. Anexe authenticate à instrução import já existente:

views/reauth.html (link em inglês)

import { _fetch, authenticate } from '/client.js';
  1. Invoque authenticate() quando o usuário tocar em Authenticate para iniciar a autenticação biométrica.

A falha na autenticação biométrica precisa acionar o uso do formulário da senha.

views/reauth.html (link em inglês)

const button = document.querySelector('#reauth');
button.addEventListener('click', e => {
  authenticate().then(user => {
    if (user) {
      location.href = '/home';
    } else {
      throw 'User not found.';
    }
  }).catch(e => {
    console.error(e.message || e);
    alert('Authentication failed. Use password to sign-in.');
    form.classList.remove('hidden');
    document.querySelector('#uvpa_available').classList.add('hidden');
  });
});

Código final desta seção

views/reauth.html (link em inglês)

...
    <main class="content">
      <div id="uvpa_available" class="hidden">
        <h2>
          Verify your identity
        </h2>
        <div>
          <mwc-button id="reauth" raised>Authenticate</mwc-button>
        </div>
        <div>
          <mwc-button id="cancel">Sign-in with password</mwc-button>
        </div>
      </div>
      <form id="form" method="POST" action="/auth/password" class="hidden">
        <h2>
          Enter a password
        </h2>
        <input type="hidden" name="username" value="{{username}}" />
        <div class="mdc-text-field mdc-text-field--filled">
          <span class="mdc-text-field__ripple"></span>
          <label class="mdc-floating-label" id="password-label">password</label>
          <input type="password" class="mdc-text-field__input" aria-labelledby="password-label" name="password" />
          <span class="mdc-line-ripple"></span>
        </div>
        <input type="submit" class="mdc-button mdc-button--raised" value="Sign-In" />
        <p class="instructions">password will be ignored in this demo.</p>
      </form>
    </main>
    <script src="https://unpkg.com/[email protected]/dist/material-components-web.min.js"></script>
    <script type="module">
      new mdc.textField.MDCTextField(document.querySelector('.mdc-text-field'));
      import { _fetch, authenticate } from '/client.js';
      const form = document.querySelector('#form');
      form.addEventListener('submit', e => {
        e.preventDefault();
        const form = new FormData(e.target);
        const cred = {};
        form.forEach((v, k) => cred[k] = v);
        _fetch(e.target.action, cred)
        .then(user => {
          location.href = '/home';
        })
        .catch(e => alert(e));
      });

      if (window.PublicKeyCredential) {
        PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
        .then(uvpaa => {
          if (uvpaa && localStorage.getItem(`credId`)) {
            document
              .querySelector('#uvpa_available')
              .classList.remove('hidden');
          } else {
            form.classList.remove('hidden');
          }
        });
      } else {
        form.classList.remove('hidden');
      }

      const cancel = document.querySelector('#cancel');
      cancel.addEventListener('click', e => {
        form.classList.remove('hidden');
        document
          .querySelector('#uvpa_available')
          .classList.add('hidden');
      });

      const button = document.querySelector('#reauth');
      button.addEventListener('click', e => {
        authenticate().then(user => {
          if (user) {
            location.href = '/home';
          } else {
            throw 'User not found.';
          }
        }).catch(e => {
          console.error(e.message || e);
          alert('Authentication failed. Use password to sign-in.');
          form.classList.remove('hidden');
          document.querySelector('#uvpa_available').classList.add('hidden');
        });
      });
    </script>
...

7. Parabéns!

Você concluiu este codelab.

Saiba mais

Agradecimentos especiais a Yuriy Ackermann, da FIDO Alliance, pela ajuda.