Ativar chaves de acesso da API de Autenticação da Web (WebAuthn)

As chaves de acesso fornecem um método de autenticação moderno e resistente a phishing baseado nos padrões Web Authentication API (WebAuthn) e FIDO2 . Eles são uma alternativa segura para senhas, usando criptografia de chave pública e autenticação baseada em dispositivo. Este artigo explica como configurar um aplicativo ASP.NET Core para usar chaves de acesso para autenticar usuários.

Para obter orientações específicas para s novos e existentes Blazor Web App, consulte Implementar chaves de acesso no ASP.NET Core Blazor Web Apps depois de ler este artigo.

O que são chaves de acesso?

As chaves de acesso substituem as palavras-passe que utilizam pares de chaves criptográficas. A chave privada é armazenada de forma segura no dispositivo do utilizador, como num módulo de segurança de hardware, autenticador de plataforma (exemplos: Windows Hello, Touch ID, Face ID) ou num gestor de palavras-passe, enquanto a chave pública é armazenada pela aplicação Web. Durante a autenticação, o usuário prova a posse da chave privada sem que ela saia do seu dispositivo.

Os principais benefícios das chaves de acesso incluem:

  • Resistência a phishing: as chaves de acesso estão vinculadas a sites específicos e não podem ser usadas em sites falsos.
  • Sem segredos compartilhados: o servidor armazena apenas chaves públicas, eliminando o risco de violações do banco de dados de senhas.
  • Conveniência do usuário: a verificação biométrica simples ou de PIN substitui os requisitos complexos de senha.
  • Sincronização entre dispositivos: muitos provedores de chaves de acesso sincronizam credenciais nos dispositivos de um usuário.

Para obter mais informações, consulte API de autenticação da Web (documentação MDN).

Chaves de acesso no ASP.NET Core Identity

ASP.NET Core Identity inclui suporte integrado para registro e autenticação de chave de acesso:

  • Integração perfeita com Identity a infraestrutura.
  • Suporte de autenticação de usuário para os cenários WebAuthn mais comuns.
  • Incorporado no modelo de projeto, portanto, apenas a configuração do Blazor Web App desenvolvedor é necessária.

Importante

A implementação de chave de acesso no ASP.NET Core Identity é deliberadamente direcionada para cenários de autenticação. Não se destina a ser uma biblioteca WebAuthn de uso geral. Os desenvolvedores que exigem funcionalidade completa do WebAuthn devem considerar bibliotecas comunitárias que fornecem suporte abrangente a protocolos.

Cenários suportados

A implementação de chave de acesso ASP.NET Core Identity suporta os seguintes cenários principais:

  • Adicionar chaves de acesso a contas existentes: os utilizadores com contas baseadas em palavra-passe podem registar chaves de acesso como um método de autenticação adicional.
  • Criação de conta sem senha: os usuários podem criar contas sem senha registrando uma chave de acesso na criação da conta.
  • Início de sessão sem palavra-passe: os utilizadores podem autenticar-se utilizando apenas a respetiva chave de acesso sem introduzir uma palavra-passe.

Limitações

A implementação atual tem as seguintes limitações:

  • Escopo para ASP.NET Core Identity: As APIs são projetadas especificamente para Identity cenários de autenticação.
  • Nenhuma validação de atestado padrão: a implementação não valida instruções de atestado por padrão.
  • Suporte a modelos: somente o modelo inclui suporte a Blazor Web App chaves de acesso.
  • Sem suporte 2FA integrado: as chaves de acesso são tratadas como um fator de autenticação principal, não como um segundo fator.

Conceitos-chave

Dois processos fundamentais sustentam as operações de chave de acesso: atestado e asserção.

Atestado (registo)

Atestado é o processo de criação e registro de uma nova chave de acesso. Durante o atestado, o servidor gera um desafio exclusivo que o autenticador deve incluir na credencial retornada. O autenticador cria um novo par de chaves e retorna a chave pública junto com os dados de atestado que comprovam a origem da chave. Em seguida, o servidor verifica esse atestado e armazena a chave pública para futuras tentativas de autenticação.

Asserção (autenticação)

Asserção é o processo de autenticação com uma chave de acesso existente. O servidor gera um desafio único, que o autenticador assina usando a chave privada. O autenticador retorna essa declaração assinada para o servidor, que verifica a assinatura usando a chave pública armazenada anteriormente. Se a assinatura for válida, o usuário será autenticado.

Pré-requisitos

  • SDK .NET (.NET 10 ou posterior)
  • Um navegador da Web moderno que suporta WebAuthn.
  • Um dispositivo com um autenticador de plataforma, como o Windows Hello ou o enclave seguro da Apple, ou uma chave de segurança.

Considerações de segurança

Ao implementar chaves de acesso no ASP.NET Core Identity, verifique se o aplicativo atende aos requisitos de segurança descritos nesta seção.

Validação de cabeçalho do host

A implementação infere a ID da Terceira Parte Confiável do cabeçalho do host quando ServerDomain não está explicitamente configurada. O ambiente de hospedagem deve validar cabeçalhos de host para evitar ataques de escopo de credenciais, que envolvem o uso de credenciais de usuário comprometidas ou roubadas (nomes de usuário, senhas, tokens) para obter acesso não autorizado.

Atenuação: configure ServerDomainIdentityPasskeyOptions explicitamente ou assegure-se de que o ambiente de hospedagem (Kestrel, IIS, proxy reverso) valide cabeçalhos de host. Para obter detalhes de configuração, consulte a documentação da sua plataforma de hospedagem.

Segurança do subdomínio

A implementação de chaves de acesso do ASP.NET Core lida com a segurança do subdomínio por meio da ServerDomain opção de configuração. Quando ServerDomain não é explicitamente especificado, a implementação usa o cabeçalho do host para determinar o domínio. Isso significa que a página na qual a chave de acesso foi registrada controla o domínio dessa credencial.

Por exemplo:

  • Se uma chave de acesso estiver registrada no app.contoso.com, ela também funcionará no *.app.contoso.com.
  • Se registrado no contoso.com, ele também funciona em *.contoso.com.
  • O navegador impõe que as chaves de acesso só podem ser usadas no domínio (e subdomínios) onde foram registradas.

Requisito: os aplicativos que exigem controle de domínio rigoroso devem definir ServerDomain explicitamente em vez de depender do cabeçalho do host. Não veicule conteúdo não confiável em nenhum subdomínio dentro do ServerDomain escopo. Se você não puder garantir isso, implemente a validação de origem personalizada para restringir o uso da chave de acesso a origens específicas.

Requisito HTTPS

Todas as operações de chave de acesso requerem HTTPS. A implementação armazena dados de autenticação em cookies criptografados e assinados que podem ser intercetados em conexões não criptografadas.

Requisito: Use sempre HTTPS na produção. Configure o protocolo HSTS (HTTP Strict Transport Security Protocol) para evitar ataques de downgrade de protocolo.

Recuperação de conta

A recuperação de conta é principalmente uma preocupação para aplicativos que permitem chaves de acesso como o único mecanismo de autenticação. O modelo de projeto padrão Blazor Web App já exige que os usuários configurem um método de autenticação de backup (senha ou provedor externo) ao criar uma conta, para que a recuperação de conta seja tratada por meio desses mecanismos existentes.

Recomendações:

Para aplicativos que implementam autenticação somente por chave de acesso, considere:

  • Códigos de recuperação gerados durante a criação da conta.
  • Fluxos de recuperação baseados em e-mail.
  • Registo obrigatório de múltiplas chaves de acesso.
  • Monitorando o IsBackedUp sinalizador para UserPasskeyInfo solicitar que os usuários adicionem credenciais adicionais.

Controlos administrativos

Quando um modelo de autenticador é descoberto com vulnerabilidades de segurança, talvez seja necessário revogar as credenciais afetadas. A implementação armazena o objeto de atestado completo com cada credencial, incluindo o GUID de Atestado de Autenticador (AAGUID), que é um identificador de 128 bits que indica o tipo de chave.

Implementação: extraia AAGUIDs de objetos de atestado armazenados, compare com modelos comprometidos conhecidos e revogue credenciais afetadas. A confiabilidade do AAGUID depende se seu aplicativo valida declarações de atestado. Para conectar a lógica de validação de declaração de atestado personalizada, consulte Validação de declaração de atestado personalizada. Bibliotecas de terceiros estão disponíveis para validação de atestados, como as Passkeys - FIDO2 .NET Library (WebAuthn) (passwordless-lib/fido2-net-lib repositório GitHub)†.

Advertência

†As bibliotecas de terceiros, incluindo passwordless-lib/fido2-net-liba , não pertencem nem são mantidas pela Microsoft e não são abrangidas por qualquer Contrato de Suporte ou licença da Microsoft. Tenha cuidado ao adotar uma biblioteca de terceiros, especialmente para recursos de segurança. Confirme se a biblioteca segue as especificações oficiais e adota as práticas recomendadas de segurança. Mantenha a versão da biblioteca atualizada para obter as correções de bugs mais recentes.

Limites de recursos

Para evitar ataques de esgotamento do banco de dados, os aplicativos devem impor limites no registro de chaves de acesso, como:

  • Número máximo de chaves de acesso por conta de utilizador.
  • Comprimento máximo para nomes de exibição de chave de acesso.

O Blazor Web App modelo impõe esses limites por padrão no nível do aplicativo. Para obter exemplos, consulte os seguintes Razor componentes no modelo de Blazor Web App projeto:

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Alternar entre ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Configurar opções de chave de acesso

ASP.NET Core Identity fornece várias opções para configurar o comportamento da chave de acesso através da IdentityPasskeyOptions classe, que incluem:

  • AuthenticatorTimeout: Obtém ou define o tempo que o navegador deve esperar que o autenticador forneça uma chave de acesso como um TimeSpanarquivo . Esta opção aplica-se tanto à criação de uma nova chave de acesso como à solicitação de uma chave de acesso existente. Esta opção é tratada como uma dica para o navegador, e o navegador pode ignorar a opção. O valor padrão é 5 minutos.
  • ChallengeSize: Obtém ou define o tamanho do desafio em bytes enviados ao cliente durante o atestado e a afirmação. Esta opção aplica-se tanto à criação de uma nova chave de acesso como à solicitação de uma chave de acesso existente. O valor padrão é 32 bytes.
  • ServerDomain: Obtém ou define a ID (domínio) efetiva da Terceira Parte Confiável do servidor. Isso deve ser exclusivo e será usado como a identidade para o servidor. Esta opção aplica-se tanto à criação de uma nova chave de acesso como à solicitação de uma chave de acesso existente. Se null, que é o valor padrão, a origem do servidor é usada. Para obter mais informações, consulte ID do RP do identificador da terceira parte confiável.

Exemplo de configuração:

builder.Services.Configure<IdentityPasskeyOptions>(options =>
{
    options.ServerDomain = "contoso.com";
    options.AuthenticatorTimeout = TimeSpan.FromMinutes(3);
    options.ChallengeSize = 64;
});

Para obter uma lista completa das opções de configuração, consulte IdentityPasskeyOptions. Para obter as definições padrão mais atualizadas do navegador, consulte a especificação WebAuthn do W3C.

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão de visualização do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Alternar entre ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Validação da declaração de atestado personalizado

Por padrão, o ASP.NET Core Identity não valida declarações de atestado. Isso é adequado para a maioria dos cenários de autenticação do consumidor. Se o seu aplicativo exigir a verificação das propriedades do autenticador ou se você quiser impedir que autenticadores específicos sejam usados, por exemplo, em ambientes corporativos que exigem um nível mais alto de segurança, você pode implementar a validação de atestado personalizado:

builder.Services.Configure<IdentityPasskeyOptions>(options =>
{
    options.VerifyAttestationStatement = async (context) =>
    {
        // Custom attestation validation logic
        // Return 'true' if the attestation is valid
        // Return 'false' if the attestation is invalid
        return true;
    };
});

Advertência

A validação do atestado é complexa e requer a manutenção de armazenamentos confiáveis para certificados de autenticador. Implemente a validação personalizada apenas se o seu aplicativo exigir a verificação de propriedades específicas do autenticador.

Validação de origem personalizada

A validação de origem padrão permite solicitações de subdomínios e não permite iframes de origem cruzada. Para personalizar esse comportamento:

builder.Services.Configure<IdentityPasskeyOptions>(options =>
{
    options.ValidateOrigin = async (context) =>
    {
        // Custom origin validation logic
        //   Access the origin via 'context.Origin'
        //   Access the HTTP context via 'context.HttpContext'
        // Return 'true' if the origin is valid
        // Return 'false' if the origin is invalid
        return true;
    };
});

Fluxo de registo

Esta seção percorre cada etapa do processo de registro de chave de acesso, explicando como ASP.NET Core Identity facilita a criação e o armazenamento de credenciais de chave de acesso.

sequenceDiagram
    participant Authenticator
    participant User
    participant Browser
    participant Server

    User->>Browser: Click "Add passkey"
    Browser->>Server: Request creation options
    Server->>Browser: Return creation options
    Browser->>Authenticator: Request new credential
    Authenticator->>User: Verify identity (biometric/PIN)
    User->>Authenticator: Approve
    Authenticator->>Browser: Return credential
    Browser->>Server: Submit credential
    Server->>Server: Verify and store
    Server->>Browser: Registration complete
    Browser->>User: Success message

Passo 1: Iniciar o registo

O processo de registo começa quando um utilizador decide adicionar uma chave de acesso à sua conta. Isso geralmente acontece por meio de um botão ou link na interface do usuário do aplicativo. Quando selecionado, esse elemento aciona o código JavaScript para orquestrar o fluxo de registro.

A implementação do lado do cliente varia significativamente entre aplicativos. Blazor Web App No modelo, você pode encontrar um exemplo completo no PasskeySubmit.razor.js, que mostra como um componente Web personalizado lida com o início do registro e gerencia as chamadas subsequentes da API WebAuthn.

Etapa 2: Solicitando opções de criação

Após o início do registro, o navegador deve obter opções de criação do servidor. Essas opções informam ao navegador que tipo de credencial criar e incluem parâmetros de segurança importantes, como o desafio que deve ser assinado.

Do ponto de vista do navegador, esta etapa envolve fazer uma solicitação HTTP para o servidor:

async function createCredential(headers, signal) {
  // Step 2: Request creation options from the server
  const optionsResponse = 
    await fetchWithErrorHandling('/Account/PasskeyCreationOptions', 
    {
      method: 'POST',
      headers,
      signal,
    });
  const optionsJson = await optionsResponse.json();
  const options = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
  return await navigator.credentials.create({ publicKey: options, signal });
}

O aplicativo deve definir um ponto de extremidade que gere estas opções:

app.MapPost("/Account/PasskeyCreationOptions", async (
    HttpContext context,
    UserManager<ApplicationUser> userManager,
    SignInManager<ApplicationUser> signInManager) =>
{
    var user = await userManager.GetUserAsync(context.User);

    if (user is null)
    {
        return Results.NotFound();
    }

    var userId = await userManager.GetUserIdAsync(user);
    var userName = await userManager.GetUserNameAsync(user) ?? "User";
    
    var optionsJson = await signInManager.MakePasskeyCreationOptionsAsync(new()
    {
        Id = userId,
        Name = userName,
        DisplayName = userName
    });
    
    return TypedResults.Content(optionsJson, contentType: "application/json");
});

O MakePasskeyCreationOptionsAsync método é central para este processo. O método aceita um PasskeyUserEntity que descreve o usuário para o qual a chave de acesso está sendo criada. Essa entidade contém o ID do usuário, o nome de usuário (normalmente um endereço de e-mail) e um nome de exibição legível por humanos. O método retorna uma cadeia de caracteres JSON que está em conformidade com o esquema WebAuthn PublicKeyCredentialCreationOptions , que o navegador usa na próxima etapa. Nos bastidores, esse método também armazena o estado temporário em uma autenticação cookie para garantir que a resposta do navegador corresponda a essas opções específicas.

Etapa 3: O servidor gera opções

Quando MakePasskeyCreationOptionsAsync executado, ele usa a configuração do IdentityPasskeyOptions aplicativo para determinar os parâmetros específicos para a criação de credenciais. Essas opções controlam vários aspetos do processo de criação da chave de acesso.

Você pode personalizar essas opções durante a inicialização do aplicativo. Por exemplo:

builder.Services.Configure<IdentityPasskeyOptions>(options =>
{
    options.ServerDomain = "contoso.com";
    options.AuthenticatorTimeout = TimeSpan.FromMinutes(3);
    options.UserVerificationRequirement = "required";
    options.ResidentKeyRequirement = "preferred";
});

A UserVerificationRequirement opção determina se o autenticador deve verificar a identidade do usuário (por meio de métodos biométricos ou PIN), enquanto ResidentKeyRequirement indica se a credencial deve ser detetável, permitindo a autenticação sem primeiro fornecer um nome de usuário. Para obter mais informações, consulte IdentityPasskeyOptions.

Etapa 4: O cliente solicita a credencial

Com as opções de criação disponíveis, o JavaScript do lado do cliente passa as opções para a API WebAuthn para criar uma nova credencial:

async function createCredential(headers, signal) {
  // Step 4: Parse the options and request a new credential from the authenticator
  const optionsResponse = 
    await fetchWithErrorHandling('/Account/PasskeyCreationOptions', 
    {
      method: 'POST',
      headers,
      signal,
    });
  const optionsJson = await optionsResponse.json();
  const options = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
  return await navigator.credentials.create({ publicKey: options, signal });
}

A parseCreationOptionsFromJSON função converte a resposta JSON no formato esperado pela API WebAuthn e navigator.credentials.create() inicia o processo de criação de credenciais com o autenticador.

Etapa 5: Interação do autenticador

Neste ponto, o navegador se comunica com o autenticador para criar a credencial. O autenticador solicita ao usuário a verificação, que pode envolver a digitalização de uma impressão digital, a inserção de um PIN ou o uso de reconhecimento facial. Essa interação é tratada inteiramente pelo navegador e pelo autenticador, não exigindo nenhum código do aplicativo. A experiência do usuário varia de acordo com o tipo de autenticador e os recursos da plataforma.

Etapa 6: Envio de credenciais

Depois que o autenticador cria a credencial, o navegador deve enviá-la de volta ao servidor para verificação e armazenamento. A credencial deve ser serializada para JSON antes do envio:

async function createCredential(headers, signal) {
  // Step 6: The credential is returned from navigator.credentials.create()
  // and is serialized to JSON for submission to the server
  const optionsResponse = 
    await fetchWithErrorHandling('/Account/PasskeyCreationOptions', 
    {
      method: 'POST',
      headers,
      signal,
    });
  const optionsJson = await optionsResponse.json();
  const options = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
  return await navigator.credentials.create({ publicKey: options, signal });
}

No modelo, a credencial retornada é serializada automaticamente e enviada por meio de um formulário, mas o mecanismo de envio exato varia de acordo com o Blazor Web App aplicativo.

Etapa 7: Verificação e armazenamento do servidor

Quando o servidor recebe a credencial, ele deve verificar sua validade e armazenar a chave pública para autenticação futura. É aqui que as APIs de chave de acesso do ASP.NET Core Identityse tornam cruciais.

O PerformPasskeyAttestationAsync método valida a resposta de atestado do cliente. Este processo de validação abrangente:

  • Verifica se o tipo de credencial corresponde às expectativas.
  • Valida o JSON de dados do cliente, incluindo origem e desafio.
  • Verifica os sinalizadores de dados do autenticador quanto à presença e verificação do usuário
  • Extrai e valida a chave pública.

Se todas as verificações forem aprovadas, o método retornará um PasskeyAttestationResult contendo as informações da chave de acesso verificada.

Depois que o atestado é verificado, o aplicativo usa AddOrUpdatePasskeyAsync para armazenar a chave de acesso no banco de dados:

var attestationResult = 
    await signInManager.PerformPasskeyAttestationAsync(credentialJson);

if (!attestationResult.Succeeded)
{
    return Results.BadRequest($"Error: {attestationResult.Failure.Message}");
}

var addResult = 
    await userManager.AddOrUpdatePasskeyAsync(user, attestationResult.Passkey);

if (!addResult.Succeeded)
{
    return Results.BadRequest("Failed to store passkey");
}

O armazenado UserPasskeyInfo contém todas as informações necessárias para autenticação futura, incluindo o ID da credencial, a chave pública, o contador de assinatura para proteção contra repetição e sinalizadores que indicam se o backup da chave de acesso é feito ou se é elegível para backup.

Etapa 8: Tarefas pós-registro

Depois de registrar com sucesso uma chave de acesso, os aplicativos geralmente executam tarefas adicionais para melhorar a experiência do usuário. Um padrão comum é solicitar que os usuários forneçam um nome amigável para sua chave de acesso, facilitando a identificação entre várias credenciais. A UserPasskeyInfo.Name propriedade armazena esse nome amigável, que pode ser atualizado usando o mesmo AddOrUpdatePasskeyAsync método:

passkey.Name = "My iPhone";
await userManager.AddOrUpdatePasskeyAsync(user, passkey);

Fluxo de autenticação

Esta seção explica como os usuários se autenticam com suas chaves de acesso, desde o início do processo de entrada até o estabelecimento de uma sessão autenticada.

sequenceDiagram
    participant Authenticator
    participant User
    participant Browser
    participant Server

    User->>Browser: Click "Sign in with passkey"
    Browser->>Server: Request authentication options
    Server->>Browser: Return authentication options
    Browser->>Authenticator: Request assertion
    Authenticator->>User: Verify identity
    User->>Authenticator: Approve
    Authenticator->>Browser: Return signed assertion
    Browser->>Server: Submit assertion
    Server->>Server: Verify signature
    Server->>Browser: Authentication complete
    Browser->>User: Redirect to app

Etapa 1: Iniciando a autenticação

Os usuários normalmente iniciam a autenticação por chave de acesso por meio de um botão ou link dedicado na página de login. Alguns aplicativos também suportam interface do usuário condicional, onde as chaves de acesso aparecem como sugestões de preenchimento automático no campo de nome de usuário. O método de iniciação aciona o código JavaScript que gerencia o fluxo de autenticação, semelhante ao processo de registro.

Etapa 2: Solicitando opções de autenticação

O navegador solicita opções de autenticação do servidor para iniciar o processo de autenticação. Essas opções incluem uma lista de credenciais aceitáveis e um novo desafio a ser assinado:

async function requestCredential(email, mediation, headers, signal) {
  // Step 2: Request authentication options from the server
  const optionsResponse = 
    await fetchWithErrorHandling(`/Account/PasskeyRequestOptions?username=${email}`, 
    {
      method: 'POST',
      headers,
      signal,
    });
  const optionsJson = await optionsResponse.json();
  const options = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
  return await navigator.credentials.get({ publicKey: options, mediation, signal });
}

O MakePasskeyRequestOptionsAsync método gera essas opções. Quando você fornece um usuário específico, ele inclui apenas as credenciais desse usuário na lista de permissões. Quando chamado sem um usuário, ele gera opções adequadas para interface do usuário condicional ou autenticação sem nome de usuário:

app.MapPost("/Account/PasskeyRequestOptions", async (
    SignInManager<ApplicationUser> signInManager,
    string? username) =>
{
    var user = string.IsNullOrEmpty(username) 
        ? null 
        : await userManager.FindByNameAsync(username);

    var optionsJson = await signInManager.MakePasskeyRequestOptionsAsync(user);

    return TypedResults.Content(optionsJson, contentType: "application/json");
});

Etapa 3: O servidor gera opções

O servidor gera opções de autenticação usando a mesma IdentityPasskeyOptions configuração usada durante o registro. O ServerDomain deve corresponder ao domínio onde a chave de acesso foi originalmente registrada, ou a autenticação falha. O UserVerificationRequirement determina se o autenticador deve verificar a identidade do usuário durante a autenticação.

Etapa 4: O cliente solicita a asserção

O JavaScript do lado do cliente passa as opções de autenticação para a API WebAuthn para solicitar uma asserção do autenticador:

async function requestCredential(email, mediation, headers, signal) {
  // Step 4: Parse the options and request an assertion from the authenticator
  const optionsResponse = 
    await fetchWithErrorHandling(`/Account/PasskeyRequestOptions?username=${email}`, 
    {
      method: 'POST',
      headers,
      signal,
    });
  const optionsJson = await optionsResponse.json();
  const options = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
  return await navigator.credentials.get({ publicKey: options, mediation, signal });
}

A navigator.credentials.get() chamada inicia o processo de autenticação com o autenticador, que solicita a verificação do usuário.

Etapa 5: Verificação do autenticador

O autenticador verifica a identidade do usuário e assina o desafio com a chave privada. Este processo é tratado inteiramente pelo navegador e autenticador, semelhante à etapa de verificação durante o registro. A experiência do usuário depende do tipo de autenticador e pode envolver verificação biométrica ou entrada de PIN.

Etapa 6: Envio da asserção

Depois que o autenticador cria a declaração assinada, o navegador a serializa para JSON e a envia para o servidor:

async function requestCredential(email, mediation, headers, signal) {
  // Step 6: The assertion is returned from navigator.credentials.get()
  // and is serialized to JSON for submission to the server
  const optionsResponse = 
    await fetchWithErrorHandling(`/Account/PasskeyRequestOptions?username=${email}`, 
    {
      method: 'POST',
      headers,
      signal,
    });
  const optionsJson = await optionsResponse.json();
  const options = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
  return await navigator.credentials.get({ publicKey: options, mediation, signal });
}

O mecanismo de envio varia de acordo com o aplicativo, mas normalmente envolve um envio de formulário ou uma chamada de API.

Etapa 7: Verificação do servidor

O servidor verifica a asserção para autenticar o usuário. ASP.NET Core Identity fornece o PasskeySignInAsync método, que executa o fluxo de autenticação completo em uma única chamada:

var result = await signInManager.PasskeySignInAsync(credentialJson);

if (result.Succeeded)
{
    return Results.Ok("Authentication successful");
}

return Results.Unauthorized();

O PasskeySignInAsync método chama PerformPasskeyAssertionAsync internamente para:

  • Valide a assinatura de asserção usando a chave pública armazenada.
  • Verifique se o desafio corresponde ao enviado originalmente.
  • Verifique os sinalizadores do autenticador quanto à presença e verificação do usuário.
  • Atualize o contador de assinaturas para evitar ataques de repetição.

Se todas as verificações passarem, o método entra no usuário e retorna uma SignInResult indicação de sucesso.

Para cenários que exigem mais controle, você pode usar PerformPasskeyAssertionAsync diretamente para validar a asserção sem entrar imediatamente no usuário:

Etapa 8: Estabelecimento da sessão

Após a autenticação bem-sucedida, o ASP.NET Core Identity estabelece uma sessão autenticada para o usuário. O PasskeySignInAsync método lida com isso automaticamente, criando os cookies de autenticação e as declarações necessárias. Em seguida, o aplicativo redireciona o usuário para recursos protegidos ou exibe conteúdo personalizado.

Mitigar PublicKeyCredential.toJSON erro (TypeError: Illegal invocation)

O PublicKeyCredential.toJSON método retorna uma representação JSON de um PublicKeyCredential. O método é invocado pelo gerenciador de senhas quando o aplicativo tenta serializar um PublicKeyCredential chamando JSON.stringify ao registrar ou autenticar um usuário.

Alguns gerenciadores de senhas não implementam o PublicKeyCredential.toJSON método corretamente, que é necessário para JSON.stringify funcionar ao serializar credenciais de chave de acesso. Ao registrar ou autenticar um usuário com um aplicativo baseado no modelo de Blazor Web App projeto, o seguinte erro é gerado por alguns gerenciadores de senhas ao tentar adicionar uma chave de acesso:

Error: Could not add a passkey: Illegal invocation

Até que o gerenciador de senhas selecionado seja atualizado para implementar o PublicKeyCredential.toJSON método corretamente, faça as seguintes alterações no aplicativo. O código a seguir realiza manualmente a serialização em JSON do PublicKeyCredential.

No arquivo Components/Account/Shared/PasskeySubmit.razor.js, localize o bloco de código de definição do elemento personalizado passkey-submit.

customElements.define('passkey-submit', class extends HTMLElement {
  ...
});

Adicione a seguinte convertToBase64 função ao bloco de código:

convertToBase64(o) {
  if (!o) {
    return undefined;
  }

  // Normalize Array to Uint8Array
  if (Array.isArray(o)) {
    o = Uint8Array.from(o);
  }

  // Normalize ArrayBuffer to Uint8Array
  if (o instanceof ArrayBuffer) {
    o = new Uint8Array(o);
  }

  // Convert Uint8Array to base64
  if (o instanceof Uint8Array) {
    let str = '';
    for (let i = 0; i < o.byteLength; i++) {
      str += String.fromCharCode(o[i]);
    }
    o = window.btoa(str);
  }

  if (typeof o !== 'string') {
    throw new Error("Could not convert to base64 string");
  }

  // Convert base64 to base64url
  o = o.replace(/\+/g, "-").replace(/\//g, "_").replace(/=*$/g, "");

  return o;
}

obtainAndSubmitCredential Na função do bloco de código, localize a linha que chama JSON.stringify com a credencial do usuário e remova a linha:

- const credentialJson = JSON.stringify(credential);

Substitua a linha anterior pelo seguinte código:

const credentialJson = JSON.stringify({
  authenticatorAttachment: credential.authenticatorAttachment,
  clientExtensionResults: credential.getClientExtensionResults(),
  id: credential.id,
  rawId: this.convertToBase64(credential.rawId),
  response: {
    attestationObject: this.convertToBase64(credential.response.attestationObject),
    authenticatorData: this.convertToBase64(credential.response.authenticatorData ?? 
      credential.response.getAuthenticatorData?.() ?? undefined),
    clientDataJSON: this.convertToBase64(credential.response.clientDataJSON),
    publicKey: this.convertToBase64(credential.response.getPublicKey?.() ?? undefined),
    publicKeyAlgorithm: credential.response.getPublicKeyAlgorithm?.() ?? undefined,
    transports: credential.response.getTransports?.() ?? undefined,
    signature: this.convertToBase64(credential.response.signature),
    userHandle: this.convertToBase64(credential.response.userHandle),
  },
  type: credential.type,
});

A solução alternativa anterior só é necessária até que o gerenciador de senhas seja atualizado para implementar o PublicKeyCredential.toJSON método corretamente. Recomendamos acompanhar as notas de versão do seu gerenciador de senhas e reverter as alterações anteriores após a atualização do gerenciador de senhas.

Recursos adicionais