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:
- Clique em Show > In a New Window para ver o site ativo (link em inglês).
- Digite um nome de usuário de sua preferência e clique em Next.
- 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.
- Clique em Try reauth e repita a segunda, terceira e quarta etapas.
- 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
- Acesse a página em inglês WebAuthn / FIDO2 API Codelab.
- Clique no nome do seu projeto > Remix Project para bifurcar o projeto e continuar com sua própria versão em um novo URL.
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
:
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 | ||
| Preferência para a transmissão de atestado: | ||
| Matriz de | ||
|
| Filtre os autenticadores disponíveis. Se você quiser que um autenticador seja conectado ao dispositivo, use | |
| Determine se a verificação de usuário local do autenticador é | ||
| Use |
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
- 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 deid
incluídas na matrizexcludeCredentials
:
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);
}
}
- 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": "..."
}
}
- 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,
};
}
- 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);
- 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.
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:
- Analise
window.PublicKeyCredential
para verificar se a WebAuthn está disponível. - 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
- 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);
};
- 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)}`);
};
- Anexe
unregisterCredential
à instruçãoimport
já existente.
views/home.html (link em inglês)
import { _fetch, unregisterCredential } from '/client.js';
- 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.
- Anexe
registerCredential
à instruçãoimport
já existente.
views/home.html (link em inglês)
import { _fetch, registerCredential, unregisterCredential } from '/client.js';
- Invoque
registerCredential()
com opções paranavigator.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/lit-html@1.0.0/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.
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
- 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)}`;
}
- Antes de pedir ao usuário para autenticar, peça para o servidor enviar um desafio e outros parâmetros. Chame
_fetch()
comopts
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).
- Resolva a promessa com
null
quandoallowCredentials
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
- 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 deid
incluídas na matrizallowCredentials
:
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);
}
- 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": ""
}
}
- 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,
};
}
- 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.
- 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>
- 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');
});
Invocar a autenticação biométrica
Por fim, ative a autenticação biométrica.
- Anexe
authenticate
à instruçãoimport
já existente:
views/reauth.html (link em inglês)
import { _fetch, authenticate } from '/client.js';
- 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/material-components-web@7.0.0/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
- Web Authentication: uma API para acessar as credenciais de chave pública nível 1 (link em inglês)
- Introdução à API WebAuthn (link em inglês)
- Workshop FIDO WebAuthn (link em inglês)
- Guia da WebAuthn: DUOSEC (link em inglês)
- Sua primeira API Android FIDO2
Agradecimentos especiais a Yuriy Ackermann, da FIDO Alliance, pela ajuda.