Por que essa atualização do WhatsApp interessa a quem desenvolve auth
Na minha experiência construindo sistemas de autenticação, a maioria dos times ainda trata 2FA como um PIN de 6 dígitos enviado por SMS. O WhatsApp acabou de mostrar, com 1 bilhão de usuários usando passkeys, que o caminho real é outro: biometria + chaves de acesso vinculadas ao dispositivo. Segundo o Olhardigital.com.br, a plataforma agora permite cadastrar mais de uma chave por usuário tanto no iOS quanto no Android, e subiu o nível da senha de segundo fator para suportar caracteres alfanuméricos longos e especiais.
O ponto que pouca gente comenta: isso não é marketing de segurança. É uma migração silenciosa do modelo “algo que você sabe” (PIN) para “algo que você é + algo que você tem” (biometria + chave privada no Secure Enclave/StrongBox). Para nós, devs, vale dissecar o que mudou, o que está por baixo e o que dá para reaproveitar nos nossos próprios produtos.
O que é, de fato, uma passkey
Quando o usuário cadastra uma passkey no WhatsApp, o app conversa com o sistema operacional (iOS Keychain ou Android Credential Manager) e dispara o protocolo FIDO2/WebAuthn. O dispositivo gera um par de chaves RSA ou Ed25519, guarda a privada no hardware seguro (Secure Enclave no iPhone, StrongBox/TEE nos Androids compatíveis) e envia a pública para o servidor do WhatsApp.
No login, o servidor emite um challenge. O app pede ao SO que assine o challenge usando a chave privada, mas só libera a operação depois de validar a biometria (Face ID, Touch ID, impressão digital) ou o PIN do aparelho. Repare: o WhatsApp nunca vê a biometria. Ele só recebe a assinatura criptográfica e compara com a pública que tem guardada.
Esse modelo elimina três vetores clássicos de ataque:
- SIM swap: o atacante não consegue clonar a credencial porque ela não sai do chip.
- Phishing de OTP: não há código de 6 dígitos para interceptar. A chave só funciona no domínio original registrado.
- Credential stuffing: a privada nunca trafega, então não vaza em breach.
Por que permitir múltiplas passkeys é mais complexo do que parece
Permitir mais de uma chave na mesma conta parece trivial. Não é. Cada passkey precisa de um credentialId único, e o servidor tem que manter uma relação N:1 entre credenciais e usuário. Isso significa:
- Endpoint dedicado para registro e listagem de credenciais.
- Lógica de revogação individual (perder um celular não pode derrubar todas as chaves).
- Sincronização via iCloud Keychain ou Google Password Manager — o que adiciona mais um vetor de risco: a senha da conta na nuvem.
Na prática, quando o usuário perde o aparelho, a passkey pode ser restaurada pela nuvem. Mas isso depende da conta Apple/Google estar bem protegida. Já vi gente perder acesso à conta porque a senha do iCloud era fraca — a ironia de uma chave forte depender de uma senha fraca.
Na Prática: implementando passkeys no seu próprio app
Se você está construindo um SaaS, dashboard interno ou app mobile e quer oferecer o mesmo nível de segurança do WhatsApp, o caminho é WebAuthn. Vou mostrar o fluxo mínimo viável do lado do cliente, que é onde a maioria dos devs trava.
1. Registro da passkey
// Frontend: pedindo ao navegador/SO para criar uma passkey
async function registrarPasskey(userId) {
const challenge = await fetch(`/api/webauthn/register/options?user=${userId}`)
.then(r => r.json());
const publicKey = {
challenge: base64ToBuffer(challenge.challenge),
rp: { name: "Meu App" },
user: {
id: base64ToBuffer(challenge.userId),
name: challenge.username,
displayName: challenge.username
},
pubKeyCredParams: [
{ type: "public-key", alg: -7 }, // ES256
{ type: "public-key", alg: -257 } // RS256
],
authenticatorSelection: {
authenticatorAttachment: "platform", // força o dispositivo local
userVerification: "required", // exige biometria
residentKey: "preferred"
},
timeout: 60000,
attestation: "none"
};
const credential = await navigator.credentials.create({ publicKey });
const attestation = {
id: credential.id,
rawId: bufferToBase64(credential.rawId),
response: {
attestationObject: bufferToBase64(
credential.response.attestationObject
),
clientDataJSON: bufferToBase64(
credential.response.clientDataJSON
)
},
type: credential.type
};
await fetch("/api/webauthn/register/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(attestation)
});
}
2. Login com a passkey
async function loginComPasskey(username) {
const options = await fetch(`/api/webauthn/login/options?user=${username}`)
.then(r => r.json());
const publicKey = {
challenge: base64ToBuffer(options.challenge),
allowCredentials: options.allowCredentials.map(c => ({
id: base64ToBuffer(c.id),
type: "public-key",
transports: c.transports
})),
userVerification: "required",
timeout: 60000
};
const assertion = await navigator.credentials.get({ publicKey });
await fetch("/api/webauthn/login/verify", {
method: "POST",
body: JSON.stringify({
id: assertion.id,
rawId: bufferToBase64(assertion.rawId),
response: {
authenticatorData: bufferToBase64(
assertion.response.authenticatorData
),
clientDataJSON: bufferToBase64(
assertion.response.clientDataJSON
),
signature: bufferToBase64(assertion.response.signature)
}
})
});
}
O backend precisa validar a assinatura usando a chave pública armazenada. Em Node, a lib @simplewebauthn/server resolve isso em poucas linhas. Em Python, use py_webauthn. A documentação do FIDO Alliance é o ponto de partida certo.
Comparando com o modelo antigo (PIN de 6 dígitos)
| Critério | PIN 6 dígitos (antigo) | Passkey + 2FA alfanumérico (novo) |
|---|---|---|
| Resistência a phishing | Baixa — usuário digita em qualquer tela | Alta — vinculada ao origin |
| Resistência a SIM swap | Nula — depende totalmente do SMS | Total — chave nunca sai do hardware |
| UX | Copiar código, esperar 30s | Toque + biometria, ~2 segundos |
| Recuperação de conta | SMS para novo número | Outra passkey + código alfanumérico longo |
| Custo de implementação | Baixo | Médio — exige WebAuthn no backend |
Repare no detalhe da senha alfanumérica longa. O WhatsApp finalmente abandonou o PIN de 6 dígitos como segunda camada. Isso está alinhado com a recomendação do NIST SP 800-63B desde 2017, que considera PINs curtos baseados em conhecimento como fator fraco. Para devs, a lição é: se você ainda usa “PIN de 6 dígitos” como segundo fator, está três versões atrás do estado da arte.
Erros comuns que devs cometem ao implementar isso
1. Confundir OTP com 2FA forte
Receber código por SMS ou TOTP (Google Authenticator) é 2FA, mas não é passkey. Tem phishing de tempo real, fatigue attack (bombardear o usuário até ele aceitar) e SIM swap. Se o seu sistema crítico depende disso, está aceitando um risco desnecessário.
2. Não validar o origin no servidor
No WebAuthn, o clientDataJSON carrega o origin. Se o seu backend não checa se aquele origin corresponde ao seu domínio, qualquer site pode tentar se autenticar usando credenciais registradas em outro lugar. É o erro mais comum em implementações caseiras.
3. Forçar PIN de 6 dígitos por retrocompatibilidade burra
Tem sistema que aceita só números no segundo fator “porque o legado não aceita mais”. Isso trava a evolução por 5 anos. Planeje a migração com grace period: aceita o PIN antigo mas força troca por alfanumérico no próximo login. O WhatsApp fez exatamente isso.
4. Ignorar o attestation
Quando você exige userVerification: "required", o navegador só retorna credenciais se o usuário passou pela biometria. Mas se o seu backend confia cegamente no que veio do cliente sem validar a assinatura, qualquer um pode forjar. WebAuthn sem verificação server-side é só teatro de segurança.
5. Não ter fluxo de revogação
Passkey roubada? Dispositivo perdido? Você precisa permitir que o usuário remova credenciais antigas. Esquecer isso é criar um ponto cego onde o atacante tem acesso permanente.
O que isso muda para o seu próximo projeto
Se você está começando um produto novo hoje, não tem desculpa: implemente WebAuthn desde o dia 1. As APIs estão maduras, Safari suporta desde 2020, Chrome desde 2019, Firefox desde 2021. A fricção para o usuário final caiu a ponto de ser inferior à do OTP.
Para sistemas legados, faça a migração em duas ondas: primeiro habilite passkeys como método opcional, depois force a migração do PIN antigo para senha alfanumérica, e por fim desative SMS como segundo fator. É o mesmo caminho que o WhatsApp vem fazendo há dois anos.
E sobre as chamadas de números desconhecidos com mais detalhes no Android: pouca gente comenta, mas isso é engenharia social combatida na UX. Mostrar país, região e旗 quantidade de reports dá ao usuário contexto para decidir atender. Devs que constroem painéis internos, sistemas de CRM ou apps de atendimento podem copiar esse padrão — exibir reputação do número antes do usuário decidir agir.
FAQ
Passkey substitui senha ou é um segundo fator?
Depende do design. O WhatsApp trata passkey como recuperação de conta e o segundo fator alfanumérico como camada extra. Em sistemas novos, você pode tornar a senha primária desnecessária — a passkey vira o único fator, e biometria fica como prova de presença.
Funciona em navegadores antigos ou só em apps?
WebAuthn é suportado por todos os navegadores modernos desde 2020. Para apps nativos, iOS usa AuthenticationServices e Android usa Credential Manager. Navegadores antigos caem no fallback de OTP, que você mantém por compatibilidade.
E se o usuário trocar de celular?
iCloud Keychain e Google Password Manager sincronizam as passkeys entre dispositivos da mesma conta. Mas se o usuário perder acesso à conta na nuvem, perdeu tudo. Por isso o WhatsApp mantém o segundo fator alfanumérico como rede de segurança.
WebAuthn é mais caro para rodar no servidor?
Não significativamente. Validação de assinatura ECDSA/RSA consome poucos milissegundos. O custo real está na complexidade inicial de implementação e na gestão de credenciais no banco. Uma vez no ar, é mais barato que gerenciar resets de senha.
Vale a pena para um SaaS pequeno?
Se você lida com dados sensíveis ou B2B, sim. Para um projeto pessoal ou hobby, talvez não — o ROI aparece quando você precisa reduzir tickets de “esqueci minha senha” e suportar phishing como vetor real de ataque.
Gostou? Me segue no GitHub e deixa um comentário se tiver dúvida ou quiser aprofundar algum ponto.