# Passkeys/WebAuthn - Autenticação Sem Senhas (.NET 10)

> **Novidade .NET 10:** Suporte nativo a Passkeys/WebAuthn integrado no ASP.NET Core Identity.

---

## 🎯 O Que São Passkeys?

**Passkeys** são credenciais criptográficas que substituem senhas tradicionais.

### Características

| Aspecto | Descrição |
|---------|-----------|
| **Segurança** | Resistentes a phishing, não podem ser roubadas |
| **Usabilidade** | Login com biometria ou PIN do dispositivo |
| **Privacidade** | Chave privada nunca sai do dispositivo |
| **Padrão** | WebAuthn (W3C) + FIDO2 |

### Como Funciona

```
1. Usuário solicita criação de passkey
2. Sistema gera par de chaves (pública/privada)
3. Chave pública → Servidor
4. Chave privada → Authenticator (Windows Hello, smartphone, security key)
5. Login: Servidor desafia → Authenticator assina → Servidor verifica
```

**Authenticators suportados:**
- Windows Hello
- Face ID / Touch ID (Apple)
- Biometria Android
- Security keys (YubiKey, etc.)

---

## 🚀 Setup em Blazor Web App

### 1. Criar Projeto com Identity

```bash
dotnet new blazor --auth Individual -o MyApp
cd MyApp
```

**Importante:** Escolher "Individual User Accounts" habilita passkeys automaticamente.

### 2. Estrutura Gerada

```
MyApp/
├── Components/
│   └── Account/
│       ├── Pages/
│       │   ├── Manage/
│       │   │   └── Passkeys.razor  ← Gerenciamento de passkeys
│       │   ├── Login.razor         ← Login com passkey
│       │   └── Register.razor
│       └── IdentityUserAccessor.cs
├── Data/
│   ├── ApplicationDbContext.cs
│   └── ApplicationUser.cs
└── Program.cs
```

### 3. Configuração Automática no Program.cs

```csharp
// Já vem configurado no template
builder.Services.AddIdentityCore<ApplicationUser>(options =>
{
    options.SignIn.RequireConfirmedAccount = true;

    // Passkeys habilitados por padrão
    options.Stores.MaxLengthForKeys = 128;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();

// WebAuthn/Passkeys configurado automaticamente
builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = IdentityConstants.ApplicationScheme;
    options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies();
```

---

## 📱 Fluxo de Uso

### Criar Passkey

1. Usuário loga com email/senha (primeira vez)
2. Acessa perfil → Aba "Passkeys"
3. Clica "Add Passkey"
4. Sistema solicita authenticator (Windows Hello, celular, etc.)
5. Usuário confirma identidade (biometria/PIN)
6. Passkey é criada e nomeada
7. Salva no banco de dados

### Login com Passkey

1. Usuário acessa página de login
2. Clica "Login with Passkey"
3. Sistema envia desafio
4. Authenticator assina desafio
5. Servidor verifica assinatura
6. Usuário autenticado

---

## 💻 Implementação Customizada

### Adicionar Passkeys a Projeto Existente

Se você tem um projeto Blazor sem passkeys, use o **scaffolder**:

```bash
dotnet tool install -g dotnet-aspnet-codegenerator

dotnet aspnet-codegenerator identity \
    --useDefaultUI \
    --dbContext ApplicationDbContext \
    --files "Account.Manage.Passkeys;Account.Login"
```

Isso adiciona:
- `Components/Account/Pages/Manage/Passkeys.razor`
- `Components/Account/Pages/Login.razor` (atualizado)

### Verificar Suporte a Passkeys

```csharp
@inject IPasskeyService PasskeyService

@code {
    private bool _supportsPasskeys;

    protected override async Task OnInitializedAsync()
    {
        _supportsPasskeys = await PasskeyService.IsSupportedAsync();
    }
}
```

### Listar Passkeys do Usuário

```csharp
@page "/manage/passkeys"
@inject IPasskeyService PasskeyService
@inject UserManager<ApplicationUser> UserManager

<h3>Minhas Passkeys</h3>

@if (_passkeys is null)
{
    <p>Carregando...</p>
}
else if (!_passkeys.Any())
{
    <p>Você não tem passkeys configuradas.</p>
}
else
{
    <ul>
        @foreach (var passkey in _passkeys)
        {
            <li>
                @passkey.Name (@passkey.CreatedAt.ToString("dd/MM/yyyy"))
                <button @onclick="() => RemovePasskey(passkey.Id)">Remover</button>
            </li>
        }
    </ul>
}

<button @onclick="AddPasskey">Adicionar Passkey</button>

@code {
    private List<Passkey>? _passkeys;

    protected override async Task OnInitializedAsync()
    {
        var user = await UserManager.GetUserAsync(User);
        _passkeys = await PasskeyService.GetUserPasskeysAsync(user!.Id);
    }

    private async Task AddPasskey()
    {
        var user = await UserManager.GetUserAsync(User);
        await PasskeyService.CreatePasskeyAsync(user!.Id);
        await OnInitializedAsync(); // Recarregar lista
    }

    private async Task RemovePasskey(string passkeyId)
    {
        await PasskeyService.RemovePasskeyAsync(passkeyId);
        await OnInitializedAsync(); // Recarregar lista
    }
}
```

---

## 🗄️ Modelo de Dados

### Entidade Passkey

```csharp
public class Passkey
{
    public string Id { get; set; } = Guid.NewGuid().ToString();
    public string UserId { get; set; } = null!;
    public string Name { get; set; } = "Passkey";
    public byte[] CredentialId { get; set; } = Array.Empty<byte>();
    public byte[] PublicKey { get; set; } = Array.Empty<byte>();
    public int SignatureCounter { get; set; }
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
    public DateTime? LastUsedAt { get; set; }

    // Relacionamento
    public ApplicationUser User { get; set; } = null!;
}
```

### DbContext

```csharp
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public DbSet<Passkey> Passkeys { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<Passkey>(entity =>
        {
            entity.HasKey(p => p.Id);
            entity.HasIndex(p => p.UserId);
            entity.HasIndex(p => p.CredentialId).IsUnique();

            entity.HasOne(p => p.User)
                .WithMany()
                .HasForeignKey(p => p.UserId)
                .OnDelete(DeleteBehavior.Cascade);
        });
    }
}
```

### Migration

```bash
dotnet ef migrations add AddPasskeys
dotnet ef database update
```

---

## 🔒 Segurança

### Configurações Recomendadas

```csharp
builder.Services.AddAuthentication()
    .AddWebAuthn(options =>
    {
        // Nome do site (aparece no authenticator)
        options.RelyingPartyId = "myapp.com";
        options.RelyingPartyName = "My Application";

        // Origem permitida
        options.Origins = new[] { "https://myapp.com" };

        // Timeout de autenticação
        options.Timeout = TimeSpan.FromSeconds(60);

        // Tipo de authenticator
        options.AuthenticatorSelection = new()
        {
            RequireResidentKey = false,
            UserVerification = UserVerificationRequirement.Preferred
        };
    });
```

### User Verification

| Modo | Descrição | Quando Usar |
|------|-----------|-------------|
| `Required` | Exige biometria/PIN sempre | Alto risco |
| `Preferred` | Prefere biometria mas aceita alternativa | Padrão recomendado |
| `Discouraged` | Não solicita verificação | Não use |

### Attestation

```csharp
options.Attestation = AttestationConveyancePreference.None;
```

**Valores:**
- `None`: Não requer attestation (mais compatível)
- `Indirect`: Valida authenticator via CA
- `Direct`: Requer attestation direta (mais restritivo)

**Recomendação:** Use `None` para máxima compatibilidade.

---

## 🧪 Testando Passkeys

### Ambiente de Desenvolvimento

1. **HTTPS obrigatório:** WebAuthn só funciona com HTTPS (ou localhost)
2. **Windows Hello:** Habilite no Windows
3. **Chrome DevTools:** Use Virtual Authenticator para testes

### Virtual Authenticator (Chrome)

1. F12 → More Tools → WebAuthn
2. Enable virtual authenticator environment
3. Add authenticator (escolha tipo: USB, NFC, Internal)
4. Teste criação e login

### Smartphone como Authenticator

1. Use HTTPS (não localhost)
2. Escaneie QR code gerado
3. Confirme com biometria do celular

---

## 🐛 Troubleshooting

### Erro: "WebAuthn not supported"

**Causa:** Navegador antigo ou HTTP (não HTTPS)

**Solução:**
- Use HTTPS em produção
- Localhost funciona com HTTP (somente dev)
- Atualize navegador

### Erro: "User verification failed"

**Causa:** Authenticator não configurado ou cancelado

**Solução:**
- Configure Windows Hello / Touch ID
- Tente outro authenticator
- Verifique `UserVerification = Preferred`

### Passkey não aparece em outro dispositivo

**Causa:** Passkeys não sincronizam automaticamente (depende do authenticator)

**Solução:**
- Use authenticator com sync (ex: Google Password Manager)
- Ou crie passkey separada por dispositivo

---

## 📊 Migração Gradual

### Permitir Email/Senha + Passkeys

```csharp
// Login.razor
<EditForm Model="Input" OnValidSubmit="LoginAsync">
    <InputText @bind-Value="Input.Email" />
    <InputText @bind-Value="Input.Password" type="password" />
    <button type="submit">Login with Password</button>
</EditForm>

<hr />

<button @onclick="LoginWithPasskey">Login with Passkey</button>

@code {
    private async Task LoginAsync()
    {
        // Login tradicional com senha
    }

    private async Task LoginWithPasskey()
    {
        // Login com WebAuthn
    }
}
```

**Estratégia:** Mantenha senha como fallback, incentive passkeys.

---

## ✅ Checklist de Implementação

- [ ] Projeto criado com `--auth Individual` ou scaffolder usado
- [ ] DbContext inclui tabela `Passkeys`
- [ ] Migration executada
- [ ] HTTPS habilitado (dev e prod)
- [ ] Página de gerenciamento de passkeys funcional
- [ ] Login com passkey funcional
- [ ] Fallback para email/senha disponível
- [ ] Testado com Windows Hello ou smartphone
- [ ] `RelyingPartyId` configurado corretamente

---

## 📚 Referências

- [WebAuthn Specification (W3C)](https://w3c.github.io/webauthn/)
- [FIDO Alliance](https://fidoalliance.org/)
- [ASP.NET Core Identity - Passkeys](https://learn.microsoft.com/aspnet/core/security/authentication/identity/passkeys)
- [Chrome WebAuthn DevTools](https://developer.chrome.com/docs/devtools/webauthn/)

---

*MORPH-SPEC by Polymorphism Tech*
