{"id":719,"date":"2026-09-13T18:35:17","date_gmt":"2026-09-13T21:35:17","guid":{"rendered":"https:\/\/yellowkode.com\/blog\/friccao-em-autenticacao-webauthn-conditional-ui\/"},"modified":"2026-09-13T18:35:17","modified_gmt":"2026-09-13T21:35:17","slug":"friccao-em-autenticacao-webauthn-conditional-ui","status":"publish","type":"post","link":"https:\/\/yellowkode.com\/blog\/friccao-em-autenticacao-webauthn-conditional-ui\/","title":{"rendered":"Fric\u00e7\u00e3o em autentica\u00e7\u00e3o: onde o login pune o usu\u00e1rio e como o WebAuthn conditional UI muda o mecanismo"},"content":{"rendered":"<p>O UX Collective publicou em 2025 o artigo <em>How authentication friction frustrates users and how to stop it<\/em>, discutindo por que telas de login continuam sendo um dos maiores pontos de abandono de produto digital. Este artigo explora como a documenta\u00e7\u00e3o de WebAuthn, relat\u00f3rios de convers\u00e3o de login e a experi\u00eancia em construir gateways de autentica\u00e7\u00e3o sustentam as discuss\u00f5es sobre fric\u00e7\u00e3o em autentica\u00e7\u00e3o.<\/p>\n<p>O ponto que quero puxar n\u00e3o \u00e9 o de sempre, que autentica\u00e7\u00e3o atrapalha. \u00c9 o mec\u00e2nico: onde exatamente, dentro do fluxo de login, o sistema decide punir o usu\u00e1rio por uma limita\u00e7\u00e3o que \u00e9 do pr\u00f3prio sistema. E o que mudou nos \u00faltimos anos que permite n\u00e3o fazer mais isso.<\/p>\n<h2>O que os dados dizem sobre o custo do login<\/h2>\n<p>Antes de entrar na mec\u00e2nica, vale calibrar o tamanho do problema. Pesquisas indicam que 92% dos usu\u00e1rios v\u00e3o abandonar um site em vez de resetar uma senha esquecida, e 67% dos usu\u00e1rios relatam frustra\u00e7\u00e3o com processos de login atuais em plataformas digitais. No checkout, o impacto \u00e9 direto na receita: estima-se que 22% dos compradores abandonaram o carrinho porque o processo de checkout foi longo ou complicado demais.<\/p>\n<p>Esses n\u00fameros \u00e0s vezes s\u00e3o usados para vender solu\u00e7\u00e3o m\u00e1gica. N\u00e3o \u00e9 o caso aqui. Eles servem s\u00f3 para justificar por que faz sentido gastar engenharia otimizando algo que muita gente trata como formul\u00e1rio resolvido.<\/p>\n<h2>Como a fric\u00e7\u00e3o se forma dentro do c\u00f3digo<\/h2>\n<p>Fric\u00e7\u00e3o em autentica\u00e7\u00e3o n\u00e3o \u00e9 uma coisa s\u00f3. S\u00e3o pelo menos quatro mecanismos distintos que se somam, e cada um tem uma causa t\u00e9cnica diferente.<\/p>\n<h3>1. Fric\u00e7\u00e3o de digita\u00e7\u00e3o<\/h3>\n<p>Toda vez que o usu\u00e1rio precisa lembrar e digitar algo, existe uma taxa de erro. Em mobile, essa taxa \u00e9 maior porque o teclado \u00e9 menor e o autocorretor atrapalha campos de senha e email. O sistema n\u00e3o controla o dedo do usu\u00e1rio, mas controla o que o campo aceita, o que o teclado sugere e o que acontece quando o usu\u00e1rio erra.<\/p>\n<p>Do lado do HTML, isso vira atributo. Um campo de email sem <code>type=\"email\"<\/code>, sem <code>inputmode=\"email\"<\/code>, sem <code>autocomplete=\"email\"<\/code> e sem <code>autocapitalize=\"off\"<\/code> \u00e9 um campo que est\u00e1 pedindo para o usu\u00e1rio errar em mobile.<\/p>\n<pre><code>&lt;input\n type=\"email\"\n name=\"email\"\n autocomplete=\"username webauthn\"\n inputmode=\"email\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n required\n\/&gt;<\/code><\/pre>\n<h3>2. Fric\u00e7\u00e3o de mem\u00f3ria<\/h3>\n<p>O usu\u00e1rio precisa lembrar da senha. Se n\u00e3o lembra, entra num fluxo paralelo de recupera\u00e7\u00e3o, que geralmente envolve email, c\u00f3digo, redefini\u00e7\u00e3o, e volta para o login. Cada etapa \u00e9 um ponto de abandono.<\/p>\n<p>\u00c9 aqui que o passkey mudou a mec\u00e2nica. Conditional UI \u00e9 uma feature do WebAuthn que permite ao usu\u00e1rio fazer login com passkeys diretamente do menu de autofill do browser. Em vez de adicionar um bot\u00e3o separado de sign in with passkey, os passkeys aparecem ao lado das senhas salvas no mesmo dropdown familiar.<\/p>\n<p>O mecanismo por baixo: seu campo de input recebe um atributo autocomplete especial que diz ao browser para incluir passkeys nas sugest\u00f5es de autofill. Seu JavaScript inicia uma requisi\u00e7\u00e3o WebAuthn com <code>mediation: conditional<\/code>, que espera silenciosamente em background at\u00e9 o usu\u00e1rio interagir com o menu de autofill. O browser verifica se o usu\u00e1rio tem passkeys registradas para o site e, se sim, mostra ao lado das senhas salvas. Quando o usu\u00e1rio seleciona uma passkey, o browser dispara o autenticador de plataforma, Face ID, Touch ID, Windows Hello, para verifica\u00e7\u00e3o.<\/p>\n<p>Uma forma de pensar: no login com senha, o usu\u00e1rio faz o trabalho de lembrar e o servidor faz o trabalho de verificar um segredo compartilhado. Com passkey via conditional UI, o dispositivo do usu\u00e1rio guarda uma chave privada, o servidor guarda a chave p\u00fablica correspondente, e a autentica\u00e7\u00e3o \u00e9 uma assinatura criptogr\u00e1fica que o servidor consegue verificar sem nunca ver o segredo.<\/p>\n<h3>3. Fric\u00e7\u00e3o de MFA<\/h3>\n<p>MFA existe porque senha n\u00e3o basta. Mas o aumento do uso de autentica\u00e7\u00e3o em dois fatores fortaleceu a autentica\u00e7\u00e3o baseada em senha fraca e prov\u00ea mais prote\u00e7\u00e3o, mas tamb\u00e9m criou consideravelmente mais fric\u00e7\u00e3o. C\u00f3digos de uso \u00fanico enviados via SMS, apps autenticadores e quest\u00f5es baseadas em conhecimento, todos representam fric\u00e7\u00e3o para os usu\u00e1rios.<\/p>\n<p>A pergunta t\u00e9cnica correta n\u00e3o \u00e9 MFA sim ou n\u00e3o. \u00c9: em quais opera\u00e7\u00f5es o risco justifica o segundo fator, e em quais um sinal passivo j\u00e1 \u00e9 suficiente? Login de sess\u00e3o nova em device desconhecido merece MFA. Voltar em device conhecido, com cookie v\u00e1lido, IP compat\u00edvel e hor\u00e1rio coerente, provavelmente n\u00e3o.<\/p>\n<h3>4. Fric\u00e7\u00e3o de erro<\/h3>\n<p>Este \u00e9 o mecanismo mais evit\u00e1vel e o mais comum. \u00c9 o formul\u00e1rio que apaga tudo quando o usu\u00e1rio erra um campo. \u00c9 a mensagem de erro que diz apenas dados inv\u00e1lidos sem dizer qual dado. \u00c9 o backend que responde 400 sem body \u00fatil e o front que renderiza um toast gen\u00e9rico.<\/p>\n<p>Sistema bom declara onde ele para, e n\u00e3o pune a pessoa por aquilo que ele mesmo deixou acontecer. Se o backend aceita senha de 8 a 64 caracteres com s\u00edmbolo obrigat\u00f3rio, o campo tem que dizer isso antes do submit, n\u00e3o depois. Se o email j\u00e1 est\u00e1 cadastrado, a resposta tem que direcionar para login ou recupera\u00e7\u00e3o, n\u00e3o devolver <em>erro de valida\u00e7\u00e3o<\/em>.<\/p>\n<h2>O que isso significa na pr\u00e1tica<\/h2>\n<p>Uma implementa\u00e7\u00e3o m\u00ednima de conditional UI, para dar concretude:<\/p>\n<pre><code>\/\/ 1. Detecta suporte antes de tentar\nif (\n window.PublicKeyCredential &amp;&amp;\n PublicKeyCredential.isConditionalMediationAvailable\n) {\n const available =\n await PublicKeyCredential.isConditionalMediationAvailable();\n\n if (available) {\n \/\/ 2. Pede as options ao seu servidor WebAuthn\n const options = await fetch('\/webauthn\/options').then(r =&gt; r.json());\n\n \/\/ 3. Dispara a requisicao em modo conditional.\n \/\/ Ela fica pendurada ate o usuario clicar numa sugestao de autofill.\n try {\n const credential = await navigator.credentials.get({\n publicKey: options,\n mediation: 'conditional',\n });\n\n \/\/ 4. Envia a assertion assinada ao servidor para verificacao\n await fetch('\/webauthn\/verify', {\n method: 'POST',\n body: JSON.stringify(credential),\n });\n } catch (err) {\n \/\/ Nao explode a UI. O usuario ainda pode logar por senha.\n console.debug('conditional webauthn cancelado', err);\n }\n }\n}<\/code><\/pre>\n<p>Tr\u00eas detalhes importantes nesse trecho. Primeiro, o <code>isConditionalMediationAvailable<\/code> \u00e9 uma checagem pr\u00e9via obrigat\u00f3ria. Sem ela, voc\u00ea quebra o login em browser antigo. Segundo, o <code>mediation: 'conditional'<\/code> \u00e9 o que diferencia do WebAuthn modal, ele n\u00e3o abre popup, ele espera. Terceiro, o <code>catch<\/code> n\u00e3o deve mostrar erro ao usu\u00e1rio, porque o usu\u00e1rio pode simplesmente ter escolhido logar por senha, e isso \u00e9 comportamento v\u00e1lido.<\/p>\n<p>Num construtor de landing page que tirou a cria\u00e7\u00e3o de p\u00e1gina da fila da engenharia, tivemos que enfrentar o mesmo tipo de decis\u00e3o em outro contexto: quando o sistema tem dois caminhos v\u00e1lidos, o desenho da tela precisa deixar ambos vivos e n\u00e3o punir quem escolhe o menos preferido. No caso do login, isso significa que a exist\u00eancia do fluxo passkey n\u00e3o pode degradar o fluxo senha. Se o usu\u00e1rio n\u00e3o tem passkey, ele n\u00e3o pode nem perceber que existe.<\/p>\n<h2>Onde essa abordagem quebra<\/h2>\n<p>Conditional UI resolve fric\u00e7\u00e3o real, mas tem limites concretos que valem a pena conhecer antes de decidir se vai gastar sprint com isso.<\/p>\n<p><strong>Compatibilidade de sistema operacional.<\/strong> Windows 10 n\u00e3o suporta Conditional UI, ent\u00e3o foi testada a op\u00e7\u00e3o de autocomplete Use a passkey que facilita o fluxo de Cross-Device Authentication. Se sua base tem parcela relevante de Windows 10, voc\u00ea precisa desenhar o fallback, n\u00e3o pode assumir que o autofill vai aparecer.<\/p>\n<p><strong>Interfer\u00eancia de gerenciadores de senha.<\/strong> Se voc\u00ea usa uma extens\u00e3o de gerenciador de senha, ela pode sequestrar o autofill. Isso pode interferir no fluxo de Conditional UI e prolongar o processo de autentica\u00e7\u00e3o. Isso n\u00e3o \u00e9 bug seu, mas o usu\u00e1rio vai reportar como bug seu.<\/p>\n<p><strong>Ordem do atributo autocomplete importa.<\/strong> O token webauthn no autocomplete habilita Conditional UI e deve aparecer no fim da string autocomplete, porque a gram\u00e1tica do atributo faz parse da direita para a esquerda. Ou seja, \u00e9 <code>autocomplete=\"username webauthn\"<\/code> e n\u00e3o <code>autocomplete=\"webauthn username\"<\/code>. \u00c9 o tipo de detalhe que quebra silencioso.<\/p>\n<p><strong>Passkey sem discoverable credential n\u00e3o aparece.<\/strong> Conditional UI s\u00f3 sugere passkeys que est\u00e3o como resident key no autenticador. Se seu servidor criou credencial n\u00e3o discoverable, ela n\u00e3o vai aparecer no autofill, mesmo estando registrada.<\/p>\n<p><strong>MFA passivo depende de sinal.<\/strong> Reduzir fric\u00e7\u00e3o via risk score, tipo <em>este usu\u00e1rio est\u00e1 no mesmo device e IP de sempre, n\u00e3o pede segundo fator<\/em>, funciona bem em produto com volume. Em produto novo, voc\u00ea n\u00e3o tem hist\u00f3rico suficiente para o modelo decidir. Nesse caso, cai no default conservador e a fric\u00e7\u00e3o volta.<\/p>\n<p>Num dashboard denso em dados de medi\u00e7\u00e3o de m\u00eddia, aprendi que a decis\u00e3o de onde colocar fric\u00e7\u00e3o n\u00e3o \u00e9 uma escolha bin\u00e1ria, \u00e9 uma calibragem por opera\u00e7\u00e3o. Login inicial pode ser mais rigoroso, a\u00e7\u00e3o destrutiva pode pedir reautentica\u00e7\u00e3o, leitura pode ser silenciosa. O mesmo racioc\u00ednio se aplica aqui: fric\u00e7\u00e3o n\u00e3o \u00e9 ruim, fric\u00e7\u00e3o mal colocada \u00e9.<\/p>\n<h2>O que n\u00e3o d\u00e1 para afirmar ainda<\/h2>\n<p>N\u00e3o consegui ler o artigo original do UX Collective, ent\u00e3o n\u00e3o sei quais casos espec\u00edficos ele cita nem qual solu\u00e7\u00e3o ele prioriza. Se ele defende, por exemplo, magic link como padr\u00e3o em vez de passkey, essa \u00e9 uma discuss\u00e3o diferente da que fiz aqui, com trade-offs pr\u00f3prios (magic link tem lat\u00eancia de email e problema de deliverability).<\/p>\n<p>Tamb\u00e9m n\u00e3o testei o comportamento de conditional UI em todos os browsers combinados com todos os gerenciadores de senha do mercado. As documenta\u00e7\u00f5es concordam que o suporte cresceu, mas o comportamento fino, quem ganha a briga pelo autofill quando extens\u00e3o e browser oferecem sugest\u00f5es concorrentes, varia. Se voc\u00ea vai colocar isso em produ\u00e7\u00e3o com base grande, teste na sua matriz real de browsers antes.<\/p>\n<p>E n\u00e3o d\u00e1 para prometer n\u00famero de convers\u00e3o. Os 92% de abandono por reset de senha s\u00e3o de pesquisa agregada. Seu produto pode ter usu\u00e1rios muito mais tolerantes ou muito menos, dependendo do valor percebido por tr\u00e1s do login.<\/p>\n<h2>Como testar isso voc\u00ea mesmo<\/h2>\n<p>Um caminho pequeno para experimentar sem montar servidor WebAuthn do zero:<\/p>\n<ol>\n<li>Crie uma p\u00e1gina HTML com um input de email, com <code>autocomplete=\"username webauthn\"<\/code>.<\/li>\n<li>Use uma biblioteca WebAuthn pronta no backend (SimpleWebAuthn em Node, py_webauthn em Python) para gerar as options.<\/li>\n<li>Registre uma passkey de teste no seu pr\u00f3prio device via fluxo modal de registro.<\/li>\n<li>Recarregue a p\u00e1gina de login e clique no input. Voc\u00ea deveria ver a passkey aparecer no autofill do browser.<\/li>\n<li>Abra o mesmo endere\u00e7o em browser sem passkey registrada. Voc\u00ea n\u00e3o deve ver nada de diferente do login tradicional. Se ver, seu fallback est\u00e1 errado.<\/li>\n<\/ol>\n<p>Esse \u00faltimo passo \u00e9 o mais importante e o mais esquecido. A prova de que a implementa\u00e7\u00e3o est\u00e1 boa n\u00e3o \u00e9 o caminho feliz funcionar, \u00e9 o caminho triste continuar funcionando igual sempre foi.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Como fric\u00e7\u00e3o em tela de login se forma no c\u00f3digo, o que muda com conditional UI do WebAuthn e onde essa abordagem ainda quebra na pr\u00e1tica.<\/p>\n","protected":false},"author":2,"featured_media":718,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-719","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/posts\/719","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/comments?post=719"}],"version-history":[{"count":0,"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/posts\/719\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/media\/718"}],"wp:attachment":[{"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/media?parent=719"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/categories?post=719"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/tags?post=719"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}