@polymorphism-tech/morph-spec 1.0.4 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1381 -0
- package/LICENSE +72 -0
- package/README.md +89 -6
- package/bin/detect-agents.js +225 -0
- package/bin/morph-spec.js +120 -0
- package/bin/render-template.js +302 -0
- package/bin/semantic-detect-agents.js +246 -0
- package/bin/validate-agents-skills.js +239 -0
- package/bin/validate-agents.js +69 -0
- package/bin/validate-phase.js +263 -0
- package/content/.azure/README.md +293 -0
- package/content/.azure/docs/azure-devops-setup.md +454 -0
- package/content/.azure/docs/branch-strategy.md +398 -0
- package/content/.azure/docs/local-development.md +515 -0
- package/content/.azure/pipelines/pipeline-variables.yml +34 -0
- package/content/.azure/pipelines/prod-pipeline.yml +319 -0
- package/content/.azure/pipelines/staging-pipeline.yml +234 -0
- package/content/.azure/pipelines/templates/build-dotnet.yml +75 -0
- package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -0
- package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -0
- package/content/.azure/pipelines/templates/infra-deploy.yml +90 -0
- package/content/.claude/commands/morph-apply.md +118 -26
- package/content/.claude/commands/morph-archive.md +9 -9
- package/content/.claude/commands/morph-clarify.md +184 -0
- package/content/.claude/commands/morph-design.md +275 -0
- package/content/.claude/commands/morph-proposal.md +56 -15
- package/content/.claude/commands/morph-setup.md +100 -0
- package/content/.claude/commands/morph-status.md +47 -32
- package/content/.claude/commands/morph-tasks.md +319 -0
- package/content/.claude/commands/morph-uiux.md +211 -0
- package/content/.claude/skills/specialists/ai-system-architect.md +604 -0
- package/content/.claude/skills/specialists/ms-agent-expert.md +143 -89
- package/content/.claude/skills/specialists/ui-ux-designer.md +744 -9
- package/content/.claude/skills/stacks/dotnet-blazor.md +244 -8
- package/content/.claude/skills/stacks/dotnet-nextjs.md +2 -2
- package/content/.morph/.morphversion +5 -0
- package/content/.morph/config/agents.json +101 -8
- package/content/.morph/config/azure-pricing.json +70 -0
- package/content/.morph/config/azure-pricing.schema.json +50 -0
- package/content/.morph/config/config.template.json +15 -3
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -0
- package/content/.morph/hooks/README.md +239 -0
- package/content/.morph/hooks/pre-commit-agents.sh +24 -0
- package/content/.morph/hooks/pre-commit-all.sh +48 -0
- package/content/.morph/hooks/pre-commit-costs.sh +91 -0
- package/content/.morph/hooks/pre-commit-specs.sh +49 -0
- package/content/.morph/hooks/pre-commit-tests.sh +60 -0
- package/content/.morph/project.md +5 -4
- package/content/.morph/schemas/agent.schema.json +296 -0
- package/content/.morph/standards/agent-framework-setup.md +453 -0
- package/content/.morph/standards/architecture.md +142 -7
- package/content/.morph/standards/azure.md +218 -23
- package/content/.morph/standards/coding.md +47 -12
- package/content/.morph/standards/dotnet10-migration.md +494 -0
- package/content/.morph/standards/fluent-ui-setup.md +590 -0
- package/content/.morph/standards/migration-guide.md +514 -0
- package/content/.morph/standards/passkeys-auth.md +423 -0
- package/content/.morph/standards/vector-search-rag.md +536 -0
- package/content/.morph/state.json +18 -0
- package/content/.morph/templates/FluentDesignTheme.cs +149 -0
- package/content/.morph/templates/MudTheme.cs +281 -0
- package/content/.morph/templates/contracts.cs +55 -55
- package/content/.morph/templates/decisions.md +4 -4
- package/content/.morph/templates/design-system.css +226 -0
- package/content/.morph/templates/infra/.dockerignore.example +89 -0
- package/content/.morph/templates/infra/Dockerfile.example +82 -0
- package/content/.morph/templates/infra/README.md +286 -0
- package/content/.morph/templates/infra/app-service.bicep +164 -0
- package/content/.morph/templates/infra/deploy.ps1 +229 -0
- package/content/.morph/templates/infra/deploy.sh +208 -0
- package/content/.morph/templates/infra/main.bicep +41 -7
- package/content/.morph/templates/infra/parameters.dev.json +6 -0
- package/content/.morph/templates/infra/parameters.prod.json +6 -0
- package/content/.morph/templates/infra/parameters.staging.json +29 -0
- package/content/.morph/templates/proposal.md +3 -3
- package/content/.morph/templates/recap.md +3 -3
- package/content/.morph/templates/spec.md +9 -8
- package/content/.morph/templates/sprint-status.yaml +68 -0
- package/content/.morph/templates/state.template.json +222 -0
- package/content/.morph/templates/story.md +143 -0
- package/content/.morph/templates/tasks.md +1 -1
- package/content/.morph/templates/ui-components.md +276 -0
- package/content/.morph/templates/ui-design-system.md +286 -0
- package/content/.morph/templates/ui-flows.md +336 -0
- package/content/.morph/templates/ui-mockups.md +133 -0
- package/content/.morph/test-infra/example.bicep +59 -0
- package/content/CLAUDE.md +124 -0
- package/content/README.md +79 -0
- package/detectors/config-detector.js +223 -0
- package/detectors/conversation-analyzer.js +163 -0
- package/detectors/index.js +84 -0
- package/detectors/standards-generator.js +275 -0
- package/detectors/structure-detector.js +221 -0
- package/docs/README.md +149 -0
- package/docs/api/cost-calculator.js.html +513 -0
- package/docs/api/design-system-generator.js.html +382 -0
- package/docs/api/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/docs/api/global.html +5263 -0
- package/docs/api/index.html +96 -0
- package/docs/api/scripts/collapse.js +39 -0
- package/docs/api/scripts/commonNav.js +28 -0
- package/docs/api/scripts/linenumber.js +25 -0
- package/docs/api/scripts/nav.js +12 -0
- package/docs/api/scripts/polyfill.js +4 -0
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/api/scripts/prettify/lang-css.js +2 -0
- package/docs/api/scripts/prettify/prettify.js +28 -0
- package/docs/api/scripts/search.js +99 -0
- package/docs/api/state-manager.js.html +423 -0
- package/docs/api/styles/jsdoc.css +776 -0
- package/docs/api/styles/prettify.css +80 -0
- package/docs/examples.md +328 -0
- package/docs/getting-started.md +302 -0
- package/docs/installation.md +361 -0
- package/docs/templates.md +418 -0
- package/docs/validation-checklist.md +266 -0
- package/package.json +39 -12
- package/src/commands/cost.js +181 -0
- package/src/commands/create-story.js +283 -0
- package/src/commands/detect.js +104 -0
- package/src/commands/doctor.js +67 -0
- package/src/commands/generate.js +149 -0
- package/src/commands/init.js +69 -45
- package/src/commands/shard-spec.js +224 -0
- package/src/commands/sprint-status.js +250 -0
- package/src/commands/state.js +333 -0
- package/src/commands/sync.js +167 -0
- package/src/commands/update-pricing.js +206 -0
- package/src/commands/update.js +88 -13
- package/src/lib/complexity-analyzer.js +292 -0
- package/src/lib/cost-calculator.js +429 -0
- package/src/lib/design-system-generator.js +298 -0
- package/src/lib/state-manager.js +340 -0
- package/src/utils/file-copier.js +59 -0
- package/src/utils/version-checker.js +175 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
# Passkeys/WebAuthn - Autenticação Sem Senhas (.NET 10)
|
|
2
|
+
|
|
3
|
+
> **Novidade .NET 10:** Suporte nativo a Passkeys/WebAuthn integrado no ASP.NET Core Identity.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🎯 O Que São Passkeys?
|
|
8
|
+
|
|
9
|
+
**Passkeys** são credenciais criptográficas que substituem senhas tradicionais.
|
|
10
|
+
|
|
11
|
+
### Características
|
|
12
|
+
|
|
13
|
+
| Aspecto | Descrição |
|
|
14
|
+
|---------|-----------|
|
|
15
|
+
| **Segurança** | Resistentes a phishing, não podem ser roubadas |
|
|
16
|
+
| **Usabilidade** | Login com biometria ou PIN do dispositivo |
|
|
17
|
+
| **Privacidade** | Chave privada nunca sai do dispositivo |
|
|
18
|
+
| **Padrão** | WebAuthn (W3C) + FIDO2 |
|
|
19
|
+
|
|
20
|
+
### Como Funciona
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
1. Usuário solicita criação de passkey
|
|
24
|
+
2. Sistema gera par de chaves (pública/privada)
|
|
25
|
+
3. Chave pública → Servidor
|
|
26
|
+
4. Chave privada → Authenticator (Windows Hello, smartphone, security key)
|
|
27
|
+
5. Login: Servidor desafia → Authenticator assina → Servidor verifica
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Authenticators suportados:**
|
|
31
|
+
- Windows Hello
|
|
32
|
+
- Face ID / Touch ID (Apple)
|
|
33
|
+
- Biometria Android
|
|
34
|
+
- Security keys (YubiKey, etc.)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 🚀 Setup em Blazor Web App
|
|
39
|
+
|
|
40
|
+
### 1. Criar Projeto com Identity
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
dotnet new blazor --auth Individual -o MyApp
|
|
44
|
+
cd MyApp
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Importante:** Escolher "Individual User Accounts" habilita passkeys automaticamente.
|
|
48
|
+
|
|
49
|
+
### 2. Estrutura Gerada
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
MyApp/
|
|
53
|
+
├── Components/
|
|
54
|
+
│ └── Account/
|
|
55
|
+
│ ├── Pages/
|
|
56
|
+
│ │ ├── Manage/
|
|
57
|
+
│ │ │ └── Passkeys.razor ← Gerenciamento de passkeys
|
|
58
|
+
│ │ ├── Login.razor ← Login com passkey
|
|
59
|
+
│ │ └── Register.razor
|
|
60
|
+
│ └── IdentityUserAccessor.cs
|
|
61
|
+
├── Data/
|
|
62
|
+
│ ├── ApplicationDbContext.cs
|
|
63
|
+
│ └── ApplicationUser.cs
|
|
64
|
+
└── Program.cs
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 3. Configuração Automática no Program.cs
|
|
68
|
+
|
|
69
|
+
```csharp
|
|
70
|
+
// Já vem configurado no template
|
|
71
|
+
builder.Services.AddIdentityCore<ApplicationUser>(options =>
|
|
72
|
+
{
|
|
73
|
+
options.SignIn.RequireConfirmedAccount = true;
|
|
74
|
+
|
|
75
|
+
// Passkeys habilitados por padrão
|
|
76
|
+
options.Stores.MaxLengthForKeys = 128;
|
|
77
|
+
})
|
|
78
|
+
.AddEntityFrameworkStores<ApplicationDbContext>()
|
|
79
|
+
.AddSignInManager()
|
|
80
|
+
.AddDefaultTokenProviders();
|
|
81
|
+
|
|
82
|
+
// WebAuthn/Passkeys configurado automaticamente
|
|
83
|
+
builder.Services.AddAuthentication(options =>
|
|
84
|
+
{
|
|
85
|
+
options.DefaultScheme = IdentityConstants.ApplicationScheme;
|
|
86
|
+
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
|
|
87
|
+
})
|
|
88
|
+
.AddIdentityCookies();
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 📱 Fluxo de Uso
|
|
94
|
+
|
|
95
|
+
### Criar Passkey
|
|
96
|
+
|
|
97
|
+
1. Usuário loga com email/senha (primeira vez)
|
|
98
|
+
2. Acessa perfil → Aba "Passkeys"
|
|
99
|
+
3. Clica "Add Passkey"
|
|
100
|
+
4. Sistema solicita authenticator (Windows Hello, celular, etc.)
|
|
101
|
+
5. Usuário confirma identidade (biometria/PIN)
|
|
102
|
+
6. Passkey é criada e nomeada
|
|
103
|
+
7. Salva no banco de dados
|
|
104
|
+
|
|
105
|
+
### Login com Passkey
|
|
106
|
+
|
|
107
|
+
1. Usuário acessa página de login
|
|
108
|
+
2. Clica "Login with Passkey"
|
|
109
|
+
3. Sistema envia desafio
|
|
110
|
+
4. Authenticator assina desafio
|
|
111
|
+
5. Servidor verifica assinatura
|
|
112
|
+
6. Usuário autenticado
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 💻 Implementação Customizada
|
|
117
|
+
|
|
118
|
+
### Adicionar Passkeys a Projeto Existente
|
|
119
|
+
|
|
120
|
+
Se você tem um projeto Blazor sem passkeys, use o **scaffolder**:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
dotnet tool install -g dotnet-aspnet-codegenerator
|
|
124
|
+
|
|
125
|
+
dotnet aspnet-codegenerator identity \
|
|
126
|
+
--useDefaultUI \
|
|
127
|
+
--dbContext ApplicationDbContext \
|
|
128
|
+
--files "Account.Manage.Passkeys;Account.Login"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Isso adiciona:
|
|
132
|
+
- `Components/Account/Pages/Manage/Passkeys.razor`
|
|
133
|
+
- `Components/Account/Pages/Login.razor` (atualizado)
|
|
134
|
+
|
|
135
|
+
### Verificar Suporte a Passkeys
|
|
136
|
+
|
|
137
|
+
```csharp
|
|
138
|
+
@inject IPasskeyService PasskeyService
|
|
139
|
+
|
|
140
|
+
@code {
|
|
141
|
+
private bool _supportsPasskeys;
|
|
142
|
+
|
|
143
|
+
protected override async Task OnInitializedAsync()
|
|
144
|
+
{
|
|
145
|
+
_supportsPasskeys = await PasskeyService.IsSupportedAsync();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Listar Passkeys do Usuário
|
|
151
|
+
|
|
152
|
+
```csharp
|
|
153
|
+
@page "/manage/passkeys"
|
|
154
|
+
@inject IPasskeyService PasskeyService
|
|
155
|
+
@inject UserManager<ApplicationUser> UserManager
|
|
156
|
+
|
|
157
|
+
<h3>Minhas Passkeys</h3>
|
|
158
|
+
|
|
159
|
+
@if (_passkeys is null)
|
|
160
|
+
{
|
|
161
|
+
<p>Carregando...</p>
|
|
162
|
+
}
|
|
163
|
+
else if (!_passkeys.Any())
|
|
164
|
+
{
|
|
165
|
+
<p>Você não tem passkeys configuradas.</p>
|
|
166
|
+
}
|
|
167
|
+
else
|
|
168
|
+
{
|
|
169
|
+
<ul>
|
|
170
|
+
@foreach (var passkey in _passkeys)
|
|
171
|
+
{
|
|
172
|
+
<li>
|
|
173
|
+
@passkey.Name (@passkey.CreatedAt.ToString("dd/MM/yyyy"))
|
|
174
|
+
<button @onclick="() => RemovePasskey(passkey.Id)">Remover</button>
|
|
175
|
+
</li>
|
|
176
|
+
}
|
|
177
|
+
</ul>
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
<button @onclick="AddPasskey">Adicionar Passkey</button>
|
|
181
|
+
|
|
182
|
+
@code {
|
|
183
|
+
private List<Passkey>? _passkeys;
|
|
184
|
+
|
|
185
|
+
protected override async Task OnInitializedAsync()
|
|
186
|
+
{
|
|
187
|
+
var user = await UserManager.GetUserAsync(User);
|
|
188
|
+
_passkeys = await PasskeyService.GetUserPasskeysAsync(user!.Id);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private async Task AddPasskey()
|
|
192
|
+
{
|
|
193
|
+
var user = await UserManager.GetUserAsync(User);
|
|
194
|
+
await PasskeyService.CreatePasskeyAsync(user!.Id);
|
|
195
|
+
await OnInitializedAsync(); // Recarregar lista
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async Task RemovePasskey(string passkeyId)
|
|
199
|
+
{
|
|
200
|
+
await PasskeyService.RemovePasskeyAsync(passkeyId);
|
|
201
|
+
await OnInitializedAsync(); // Recarregar lista
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 🗄️ Modelo de Dados
|
|
209
|
+
|
|
210
|
+
### Entidade Passkey
|
|
211
|
+
|
|
212
|
+
```csharp
|
|
213
|
+
public class Passkey
|
|
214
|
+
{
|
|
215
|
+
public string Id { get; set; } = Guid.NewGuid().ToString();
|
|
216
|
+
public string UserId { get; set; } = null!;
|
|
217
|
+
public string Name { get; set; } = "Passkey";
|
|
218
|
+
public byte[] CredentialId { get; set; } = Array.Empty<byte>();
|
|
219
|
+
public byte[] PublicKey { get; set; } = Array.Empty<byte>();
|
|
220
|
+
public int SignatureCounter { get; set; }
|
|
221
|
+
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
|
222
|
+
public DateTime? LastUsedAt { get; set; }
|
|
223
|
+
|
|
224
|
+
// Relacionamento
|
|
225
|
+
public ApplicationUser User { get; set; } = null!;
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### DbContext
|
|
230
|
+
|
|
231
|
+
```csharp
|
|
232
|
+
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
|
|
233
|
+
{
|
|
234
|
+
public DbSet<Passkey> Passkeys { get; set; }
|
|
235
|
+
|
|
236
|
+
protected override void OnModelCreating(ModelBuilder builder)
|
|
237
|
+
{
|
|
238
|
+
base.OnModelCreating(builder);
|
|
239
|
+
|
|
240
|
+
builder.Entity<Passkey>(entity =>
|
|
241
|
+
{
|
|
242
|
+
entity.HasKey(p => p.Id);
|
|
243
|
+
entity.HasIndex(p => p.UserId);
|
|
244
|
+
entity.HasIndex(p => p.CredentialId).IsUnique();
|
|
245
|
+
|
|
246
|
+
entity.HasOne(p => p.User)
|
|
247
|
+
.WithMany()
|
|
248
|
+
.HasForeignKey(p => p.UserId)
|
|
249
|
+
.OnDelete(DeleteBehavior.Cascade);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Migration
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
dotnet ef migrations add AddPasskeys
|
|
259
|
+
dotnet ef database update
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## 🔒 Segurança
|
|
265
|
+
|
|
266
|
+
### Configurações Recomendadas
|
|
267
|
+
|
|
268
|
+
```csharp
|
|
269
|
+
builder.Services.AddAuthentication()
|
|
270
|
+
.AddWebAuthn(options =>
|
|
271
|
+
{
|
|
272
|
+
// Nome do site (aparece no authenticator)
|
|
273
|
+
options.RelyingPartyId = "myapp.com";
|
|
274
|
+
options.RelyingPartyName = "My Application";
|
|
275
|
+
|
|
276
|
+
// Origem permitida
|
|
277
|
+
options.Origins = new[] { "https://myapp.com" };
|
|
278
|
+
|
|
279
|
+
// Timeout de autenticação
|
|
280
|
+
options.Timeout = TimeSpan.FromSeconds(60);
|
|
281
|
+
|
|
282
|
+
// Tipo de authenticator
|
|
283
|
+
options.AuthenticatorSelection = new()
|
|
284
|
+
{
|
|
285
|
+
RequireResidentKey = false,
|
|
286
|
+
UserVerification = UserVerificationRequirement.Preferred
|
|
287
|
+
};
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### User Verification
|
|
292
|
+
|
|
293
|
+
| Modo | Descrição | Quando Usar |
|
|
294
|
+
|------|-----------|-------------|
|
|
295
|
+
| `Required` | Exige biometria/PIN sempre | Alto risco |
|
|
296
|
+
| `Preferred` | Prefere biometria mas aceita alternativa | Padrão recomendado |
|
|
297
|
+
| `Discouraged` | Não solicita verificação | Não use |
|
|
298
|
+
|
|
299
|
+
### Attestation
|
|
300
|
+
|
|
301
|
+
```csharp
|
|
302
|
+
options.Attestation = AttestationConveyancePreference.None;
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**Valores:**
|
|
306
|
+
- `None`: Não requer attestation (mais compatível)
|
|
307
|
+
- `Indirect`: Valida authenticator via CA
|
|
308
|
+
- `Direct`: Requer attestation direta (mais restritivo)
|
|
309
|
+
|
|
310
|
+
**Recomendação:** Use `None` para máxima compatibilidade.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## 🧪 Testando Passkeys
|
|
315
|
+
|
|
316
|
+
### Ambiente de Desenvolvimento
|
|
317
|
+
|
|
318
|
+
1. **HTTPS obrigatório:** WebAuthn só funciona com HTTPS (ou localhost)
|
|
319
|
+
2. **Windows Hello:** Habilite no Windows
|
|
320
|
+
3. **Chrome DevTools:** Use Virtual Authenticator para testes
|
|
321
|
+
|
|
322
|
+
### Virtual Authenticator (Chrome)
|
|
323
|
+
|
|
324
|
+
1. F12 → More Tools → WebAuthn
|
|
325
|
+
2. Enable virtual authenticator environment
|
|
326
|
+
3. Add authenticator (escolha tipo: USB, NFC, Internal)
|
|
327
|
+
4. Teste criação e login
|
|
328
|
+
|
|
329
|
+
### Smartphone como Authenticator
|
|
330
|
+
|
|
331
|
+
1. Use HTTPS (não localhost)
|
|
332
|
+
2. Escaneie QR code gerado
|
|
333
|
+
3. Confirme com biometria do celular
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 🐛 Troubleshooting
|
|
338
|
+
|
|
339
|
+
### Erro: "WebAuthn not supported"
|
|
340
|
+
|
|
341
|
+
**Causa:** Navegador antigo ou HTTP (não HTTPS)
|
|
342
|
+
|
|
343
|
+
**Solução:**
|
|
344
|
+
- Use HTTPS em produção
|
|
345
|
+
- Localhost funciona com HTTP (somente dev)
|
|
346
|
+
- Atualize navegador
|
|
347
|
+
|
|
348
|
+
### Erro: "User verification failed"
|
|
349
|
+
|
|
350
|
+
**Causa:** Authenticator não configurado ou cancelado
|
|
351
|
+
|
|
352
|
+
**Solução:**
|
|
353
|
+
- Configure Windows Hello / Touch ID
|
|
354
|
+
- Tente outro authenticator
|
|
355
|
+
- Verifique `UserVerification = Preferred`
|
|
356
|
+
|
|
357
|
+
### Passkey não aparece em outro dispositivo
|
|
358
|
+
|
|
359
|
+
**Causa:** Passkeys não sincronizam automaticamente (depende do authenticator)
|
|
360
|
+
|
|
361
|
+
**Solução:**
|
|
362
|
+
- Use authenticator com sync (ex: Google Password Manager)
|
|
363
|
+
- Ou crie passkey separada por dispositivo
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## 📊 Migração Gradual
|
|
368
|
+
|
|
369
|
+
### Permitir Email/Senha + Passkeys
|
|
370
|
+
|
|
371
|
+
```csharp
|
|
372
|
+
// Login.razor
|
|
373
|
+
<EditForm Model="Input" OnValidSubmit="LoginAsync">
|
|
374
|
+
<InputText @bind-Value="Input.Email" />
|
|
375
|
+
<InputText @bind-Value="Input.Password" type="password" />
|
|
376
|
+
<button type="submit">Login with Password</button>
|
|
377
|
+
</EditForm>
|
|
378
|
+
|
|
379
|
+
<hr />
|
|
380
|
+
|
|
381
|
+
<button @onclick="LoginWithPasskey">Login with Passkey</button>
|
|
382
|
+
|
|
383
|
+
@code {
|
|
384
|
+
private async Task LoginAsync()
|
|
385
|
+
{
|
|
386
|
+
// Login tradicional com senha
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private async Task LoginWithPasskey()
|
|
390
|
+
{
|
|
391
|
+
// Login com WebAuthn
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**Estratégia:** Mantenha senha como fallback, incentive passkeys.
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## ✅ Checklist de Implementação
|
|
401
|
+
|
|
402
|
+
- [ ] Projeto criado com `--auth Individual` ou scaffolder usado
|
|
403
|
+
- [ ] DbContext inclui tabela `Passkeys`
|
|
404
|
+
- [ ] Migration executada
|
|
405
|
+
- [ ] HTTPS habilitado (dev e prod)
|
|
406
|
+
- [ ] Página de gerenciamento de passkeys funcional
|
|
407
|
+
- [ ] Login com passkey funcional
|
|
408
|
+
- [ ] Fallback para email/senha disponível
|
|
409
|
+
- [ ] Testado com Windows Hello ou smartphone
|
|
410
|
+
- [ ] `RelyingPartyId` configurado corretamente
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## 📚 Referências
|
|
415
|
+
|
|
416
|
+
- [WebAuthn Specification (W3C)](https://w3c.github.io/webauthn/)
|
|
417
|
+
- [FIDO Alliance](https://fidoalliance.org/)
|
|
418
|
+
- [ASP.NET Core Identity - Passkeys](https://learn.microsoft.com/aspnet/core/security/authentication/identity/passkeys)
|
|
419
|
+
- [Chrome WebAuthn DevTools](https://developer.chrome.com/docs/devtools/webauthn/)
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
*MORPH-SPEC by Polymorphism Tech*
|