A integração do ASP.NET Identity com o FIDO2 é um procedimento complexo. Este aborda essa complexidade para você implementar o FIDO2 nas suas aplicações ASP.NET.
Este post apresenta a implementação do fluxo Passwordless, que dispensa a necessidade da senha do usuário, usando o ASP.NET Identity.
Resultado final
Ao final desse post essa aplicação será construída:
Obrigado FEITIAN
Esse post só foi possivel graças a FEITIAN que me disponibilizou um device FIDO2 para testes.
Essa demo foi inteiramenta desenvolvida utilizando esse modelo BioPass FIDO Security Key
FIDO2 e WebAuthn
FIDO2 e WebAuthn são padrões de autenticação aberto. Suportado pelos navegadores e implementado por grandes empresas de tecnologia, como Microsoft, Google etc.
O objetivo principal é permitir que um usuário faça login sem senhas, criando fluxos sem senha ou com MFA.
O padrão não se limita a aplicativos da Web. Tem suporte para Active Directory e aplicativos nativos.
A tecnologia é baseado em chaves públicas/privadas, permitindo que a autenticação aconteça sem compartilhar um segredo entre o usuário e a plataforma. Isso traz muitos benefícios, como logins mais fáceis, seguros e torna as tentativas de phishing extremamente difíceis.
ASP.NET Identity
O ASP.NET Identity é um componente para gerenciamento de usuários. É práticamente um Standard no ecossistema .NET para controle de usuários.
SHOW ME THE CODE
Essa solução utiliza os componentes fido2-net-lib. É um componente que passou em todos os testes conformidade ao padrão FIDO2. Ou seja, é um componente que segue a risca todos os quesitos de segurança necessários.
O problema desse componente reside no fato dele não ser amigável para ser armazenado num banco de dados. É necessário fazer muitas conversões para que o EntityFramework, por exemplo, armazene e recupere o estado dos objetos que o fido1-net-lib
necessita.
Para contornar esse problema será utilizado o NetDevPack.Fido2.EntityFramework.Store. Um wrapper que resolve o problema de armazenamento dos componentes do fido2-net-lib
Criando a solution
Abra o Visual Studio 2022 e crie um novo projeto do tipo ASP.NET Core Web App (Model-View-Controller) com nome de Fido2.Passwordless.
Não esqueça de adicionar suporte a usuários no step Additional Information
.
Assim que carregar a solução, adicione as dependencias do Fido2.AspNet
e NetDevPack.Fido2.EntityFramework.Store
(Botão direito em cima da Solution > Add Nuget Packages).
Abra o Program.cs
e localize a configuração do ApplicationDbContext
. Abra essa classe e altere de acordo com o código abaixo:
Esse código é a implementação do NetDevPack.Fido2.EntityFramework.Store. Essa classe CredentialStore
é quem irá armazenar a chave pública das security keys FIDO2.
Ainda em Program.cs
adicione as configurações do Fido2
Nesse trecho está sendo informado ao ASP.NET para configurar Fido2
e também configurar NetDevPack.Fido2
O Fido2
também é dependente de uma configuração que vai no appsettings.json
Certifique que a porta que ASP.NET associou a sua aplicação é a mesma que está no appsettings.json
. Para isso verifique o arquivo launchSettings.json
na pasta Properties
.
Controller, View e js
Agora vem a parte pesada, um monte de código que ao final de cada um, será dado a explicação das partes mais importantes.
Crie a seguinte estrutura:
- Adicione uma
Controller
PasswordlessController.cs
- Crie uma Class na pasta Model chamado
PasswordlessModel.cs
- Adicione duas Views na pasta
Views/Passwordless
- Index.cshtml
- Login.cshtml
- Adicione dois arquivos javascript em
wwwroot/js/
- register.passwordless.js
- login.passwordless.js
- helpers.js
O resultado final será esse:
PasswordlessController.cs
Na controller PasswordlessController.cs
adicione o código conforme a seguir.
Pontos importantes:
O processo de Registrar novo usuário tem dois pontos importantes:
GetAttestationOptions
Esse método cria os parâmetros necessários para enviar ao Security Key, além de verificar se o Usuário já não possui uma chave cadastrada no site. Esses parametros são enviados ao Security Key através do WebAuthn via Javascript. Esses parametros vão instruir o device a criar uma nova chave privada e enviar a chave pública de volta para a aplicação. Que vai receber esse parametros no métodoIndex(PasswordlessModel model, string returnUrl)
.O método
Index(PasswordlessModel model, string returnUrl)
recebe a resposta que o Security Key enviou ao site através do Javascript, que por sua vez faz umPOST
para esse método. Ele confronta as informações enviadas com aquelas que salvou em "Memória" no métodoGetAttestationOptions
e também faz a validação da Assinatura que o Security Key fez através da chave pública que ele recebeu.GetAssertionOptions
Esse método cria os parâmetros necessários para o security key apenas validar o usuário, ou seja, efetuar login. O security key por sua vez recebe esses parâmetros, gera uma assinatura através da Chave Privada e envia ao Javascript através doWebAuthn
.O método
Login(PasswordlessModel.LoginModel model)
recebe a assinatura que foi gerado pelo Security Key. Então busca a chave pública desse usuário que foi informado no Site e tenta validar a Assinatura. Se a chave pública desse usuário validar essa assinatura, significa que o Security Key utilizado é de fato desse usuário, portanto efetua o Login.
register.passwordless.js
O arquivo javascript register.passwordless.js
terá o seguinte código.
Os pontos mais importantes são:
- O js intercepta o click do botão
Register
logo na primeira linha do Js. Assim ele faz umGET
no métodoGetAttestationOptions
passando as informações do usuário, como Email e Nome. Esse método como descrito acima, gera os parametros que deverá ser enviado ao Security Key através doWebAuthn
. O método também faz um parse de algumas propriedades para adequar ao padrãoWebAuthn
e chama o Security Key na linha 66
newCredential = await navigator.credentials.create({
publicKey: makeCredentialOptions
});
Assim que o Security Key responde o browser, o javascript pega a resposta, põe num campo Hidden e faz um POST
do form para o método Index(PasswordlessModel model, string returnUrl)
. Esse método além de cadastrar a chave pública do usuário, cria esse usuário com o ASP.NET Identity e segue o fluxo padrão do ASP.NET Identity.
login.passwordless.js
O arquivo javascript login.passwordless.js
terá o seguinte código.
Os pontos mais importantes são:
- O js intercepta o click do botão
Login
logo na primeira linha do Js. Assim ele faz umGET
no métodoGetAssertionOptions
passando apenas o login do usuário, nesse caso o e-mail. Esse método como descrito acima, gera os parametros que deverá ser enviado ao Security Key através doWebAuthn
. O método também faz um parse de algumas propriedades para adequar ao padrãoWebAuthn
e chama o Security Key na linha 64
credential = await navigator.credentials.get({ publicKey: makeAssertionOptions })
Assim que o Security Key responde o browser, o javascript pega a resposta, põe num campo Hidden e faz um POST
do form para o método Login(PasswordlessModel.LoginModel login, string returnUrl)
. Esse método verifica se a chave público do usuário é capaz de validar a assinatura da chave privada desse Security Key. Se conseguir validar, significa que o Security Key é de fato do usuário, portanto ele pode logar.
Demais arquivos
Os demais arquivos é um bundle de código HTML e a Model utilizado na Controller.
Index.cshtml
Login.cshtml
PasswordlessModel.cs
- helpers.js
E por fim, altere o arquivo _Layout.cshtml
para adicionar as rotas para essas páginas.
Conclusão
A integração é trabalhosa, principalmente se você abrir mão de utilizar o NetDevPack.Fido2.EntityFramework.Store
, afinal além de lidar com toda complexidade do Fido2, ainda tem que converter suas classes e armazenar em algum lugar. A exceção, seria se estiver utilizando um banco NoSQL, como o MongoDb, por exemplo.
Mas no final, é um amontoado de código para comunicar via javascript com o WebAuthn e devolver a resposta para o ASP.NET Core.
Os demos oficiais não ajudam ao excluir o ASP.NET Identity das demos, dificultando a adoção. A maioria de nós, que vamos utilizar uma abordagem Passwordless, não podemos simplesmente abrir mão da Senha em nossas aplicações por um motivo óbvio: Quantos usuários possuem uma Security Key?
Então precisamos coexistir com o ASP.NET Identity e FIDO2.