@polymorphism-tech/morph-spec 1.0.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.
Files changed (83) hide show
  1. package/README.md +279 -0
  2. package/bin/morph-spec.js +53 -0
  3. package/content/.claude/commands/morph-apply.md +66 -0
  4. package/content/.claude/commands/morph-archive.md +79 -0
  5. package/content/.claude/commands/morph-costs.md +206 -0
  6. package/content/.claude/commands/morph-infra.md +209 -0
  7. package/content/.claude/commands/morph-proposal.md +60 -0
  8. package/content/.claude/commands/morph-status.md +71 -0
  9. package/content/.claude/settings.local.json +15 -0
  10. package/content/.claude/skills/infra/bicep-architect.md +419 -0
  11. package/content/.claude/skills/infra/container-specialist.md +437 -0
  12. package/content/.claude/skills/infra/devops-engineer.md +405 -0
  13. package/content/.claude/skills/integrations/asaas-financial.md +333 -0
  14. package/content/.claude/skills/integrations/azure-identity.md +309 -0
  15. package/content/.claude/skills/integrations/clerk-auth.md +290 -0
  16. package/content/.claude/skills/specialists/azure-architect.md +142 -0
  17. package/content/.claude/skills/specialists/cost-guardian.md +110 -0
  18. package/content/.claude/skills/specialists/ef-modeler.md +200 -0
  19. package/content/.claude/skills/specialists/hangfire-orchestrator.md +245 -0
  20. package/content/.claude/skills/specialists/ms-agent-expert.md +209 -0
  21. package/content/.claude/skills/specialists/po-pm-advisor.md +197 -0
  22. package/content/.claude/skills/specialists/standards-architect.md +78 -0
  23. package/content/.claude/skills/specialists/ui-ux-designer.md +325 -0
  24. package/content/.claude/skills/stacks/dotnet-blazor.md +352 -0
  25. package/content/.claude/skills/stacks/dotnet-nextjs.md +402 -0
  26. package/content/.claude/skills/stacks/shopify.md +445 -0
  27. package/content/.morph/archive/.gitkeep +25 -0
  28. package/content/.morph/config/agents.json +149 -0
  29. package/content/.morph/config/config.template.json +96 -0
  30. package/content/.morph/examples/api-nextjs/README.md +241 -0
  31. package/content/.morph/examples/api-nextjs/contracts.ts +307 -0
  32. package/content/.morph/examples/api-nextjs/spec.md +399 -0
  33. package/content/.morph/examples/api-nextjs/tasks.md +168 -0
  34. package/content/.morph/examples/micro-saas/README.md +125 -0
  35. package/content/.morph/examples/micro-saas/contracts.cs +358 -0
  36. package/content/.morph/examples/micro-saas/decisions.md +246 -0
  37. package/content/.morph/examples/micro-saas/spec.md +236 -0
  38. package/content/.morph/examples/micro-saas/tasks.md +150 -0
  39. package/content/.morph/examples/multi-agent/README.md +309 -0
  40. package/content/.morph/examples/multi-agent/contracts.cs +433 -0
  41. package/content/.morph/examples/multi-agent/spec.md +479 -0
  42. package/content/.morph/examples/multi-agent/tasks.md +185 -0
  43. package/content/.morph/features/.gitkeep +25 -0
  44. package/content/.morph/project.md +159 -0
  45. package/content/.morph/specs/.gitkeep +20 -0
  46. package/content/.morph/standards/architecture.md +190 -0
  47. package/content/.morph/standards/azure.md +184 -0
  48. package/content/.morph/standards/coding.md +342 -0
  49. package/content/.morph/templates/agent.cs +172 -0
  50. package/content/.morph/templates/component.razor +239 -0
  51. package/content/.morph/templates/contracts.cs +217 -0
  52. package/content/.morph/templates/decisions.md +106 -0
  53. package/content/.morph/templates/infra/app-insights.bicep +63 -0
  54. package/content/.morph/templates/infra/container-app-env.bicep +49 -0
  55. package/content/.morph/templates/infra/container-app.bicep +156 -0
  56. package/content/.morph/templates/infra/key-vault.bicep +91 -0
  57. package/content/.morph/templates/infra/main.bicep +155 -0
  58. package/content/.morph/templates/infra/parameters.dev.json +23 -0
  59. package/content/.morph/templates/infra/parameters.prod.json +23 -0
  60. package/content/.morph/templates/infra/sql-database.bicep +103 -0
  61. package/content/.morph/templates/infra/storage.bicep +106 -0
  62. package/content/.morph/templates/integrations/asaas-client.cs +387 -0
  63. package/content/.morph/templates/integrations/asaas-webhook.cs +351 -0
  64. package/content/.morph/templates/integrations/azure-identity-config.cs +288 -0
  65. package/content/.morph/templates/integrations/clerk-config.cs +258 -0
  66. package/content/.morph/templates/job.cs +171 -0
  67. package/content/.morph/templates/migration.cs +83 -0
  68. package/content/.morph/templates/proposal.md +155 -0
  69. package/content/.morph/templates/recap.md +105 -0
  70. package/content/.morph/templates/repository.cs +141 -0
  71. package/content/.morph/templates/saas/subscription.cs +347 -0
  72. package/content/.morph/templates/saas/tenant.cs +338 -0
  73. package/content/.morph/templates/service.cs +139 -0
  74. package/content/.morph/templates/spec.md +147 -0
  75. package/content/.morph/templates/tasks.md +235 -0
  76. package/content/.morph/templates/test.cs +239 -0
  77. package/content/CLAUDE.md +318 -0
  78. package/package.json +50 -0
  79. package/src/commands/doctor.js +132 -0
  80. package/src/commands/init.js +121 -0
  81. package/src/commands/update.js +84 -0
  82. package/src/utils/file-copier.js +50 -0
  83. package/src/utils/logger.js +32 -0
@@ -0,0 +1,333 @@
1
+ # Asaas Financial
2
+
3
+ Especialista em integração com Asaas para pagamentos, cobranças e gestão financeira no Brasil.
4
+
5
+ ## Responsabilidades
6
+
7
+ 1. **Integrar Asaas API** para pagamentos
8
+ 2. **Criar clientes** e cobranças
9
+ 3. **Processar webhooks** de eventos
10
+ 4. **Gerenciar assinaturas** recorrentes
11
+
12
+ ## Triggers
13
+
14
+ Keywords: `asaas`, `payment`, `pix`, `boleto`, `cobranca`, `subscription`, `billing`, `pagamento`
15
+
16
+ ## Sobre o Asaas
17
+
18
+ - **Plataforma brasileira** de cobranças e pagamentos
19
+ - **Suporta**: PIX, Boleto, Cartão de Crédito
20
+ - **API REST** (sem SDK .NET oficial)
21
+ - **Webhooks** para notificações em tempo real
22
+
23
+ ## Configuração Básica
24
+
25
+ ```csharp
26
+ // appsettings.json
27
+ {
28
+ "Asaas": {
29
+ "BaseUrl": "https://sandbox.asaas.com/api/v3",
30
+ "ApiKey": "${ASAAS_API_KEY}"
31
+ }
32
+ }
33
+
34
+ // Services/AsaasOptions.cs
35
+ public class AsaasOptions
36
+ {
37
+ public string BaseUrl { get; set; } = "";
38
+ public string ApiKey { get; set; } = "";
39
+ }
40
+
41
+ // Program.cs
42
+ builder.Services.Configure<AsaasOptions>(
43
+ builder.Configuration.GetSection("Asaas"));
44
+
45
+ builder.Services.AddHttpClient<IAsaasClient, AsaasClient>((sp, client) =>
46
+ {
47
+ var options = sp.GetRequiredService<IOptions<AsaasOptions>>().Value;
48
+ client.BaseAddress = new Uri(options.BaseUrl);
49
+ client.DefaultRequestHeaders.Add("access_token", options.ApiKey);
50
+ });
51
+ ```
52
+
53
+ ## Cliente HTTP
54
+
55
+ ```csharp
56
+ // Services/AsaasClient.cs
57
+ public interface IAsaasClient
58
+ {
59
+ Task<AsaasCustomer> CreateCustomerAsync(CreateCustomerRequest request);
60
+ Task<AsaasPayment> CreatePaymentAsync(CreatePaymentRequest request);
61
+ Task<AsaasPayment> GetPaymentAsync(string paymentId);
62
+ Task<AsaasSubscription> CreateSubscriptionAsync(CreateSubscriptionRequest request);
63
+ }
64
+
65
+ public class AsaasClient : IAsaasClient
66
+ {
67
+ private readonly HttpClient _httpClient;
68
+ private readonly ILogger<AsaasClient> _logger;
69
+
70
+ public AsaasClient(HttpClient httpClient, ILogger<AsaasClient> logger)
71
+ {
72
+ _httpClient = httpClient;
73
+ _logger = logger;
74
+ }
75
+
76
+ public async Task<AsaasCustomer> CreateCustomerAsync(CreateCustomerRequest request)
77
+ {
78
+ var response = await _httpClient.PostAsJsonAsync("customers", request);
79
+ response.EnsureSuccessStatusCode();
80
+ return await response.Content.ReadFromJsonAsync<AsaasCustomer>()
81
+ ?? throw new InvalidOperationException("Failed to deserialize customer");
82
+ }
83
+
84
+ public async Task<AsaasPayment> CreatePaymentAsync(CreatePaymentRequest request)
85
+ {
86
+ _logger.LogInformation("Creating payment for customer {CustomerId}", request.Customer);
87
+
88
+ var response = await _httpClient.PostAsJsonAsync("payments", request);
89
+
90
+ if (!response.IsSuccessStatusCode)
91
+ {
92
+ var error = await response.Content.ReadAsStringAsync();
93
+ _logger.LogError("Failed to create payment: {Error}", error);
94
+ throw new AsaasException($"Failed to create payment: {error}");
95
+ }
96
+
97
+ return await response.Content.ReadFromJsonAsync<AsaasPayment>()!;
98
+ }
99
+
100
+ public async Task<AsaasPayment> GetPaymentAsync(string paymentId)
101
+ {
102
+ var response = await _httpClient.GetAsync($"payments/{paymentId}");
103
+ response.EnsureSuccessStatusCode();
104
+ return await response.Content.ReadFromJsonAsync<AsaasPayment>()!;
105
+ }
106
+
107
+ public async Task<AsaasSubscription> CreateSubscriptionAsync(CreateSubscriptionRequest request)
108
+ {
109
+ var response = await _httpClient.PostAsJsonAsync("subscriptions", request);
110
+ response.EnsureSuccessStatusCode();
111
+ return await response.Content.ReadFromJsonAsync<AsaasSubscription>()!;
112
+ }
113
+ }
114
+ ```
115
+
116
+ ## DTOs
117
+
118
+ ```csharp
119
+ // Models/Asaas/CreateCustomerRequest.cs
120
+ public record CreateCustomerRequest
121
+ {
122
+ [JsonPropertyName("name")]
123
+ public required string Name { get; init; }
124
+
125
+ [JsonPropertyName("cpfCnpj")]
126
+ public required string CpfCnpj { get; init; }
127
+
128
+ [JsonPropertyName("email")]
129
+ public string? Email { get; init; }
130
+
131
+ [JsonPropertyName("phone")]
132
+ public string? Phone { get; init; }
133
+
134
+ [JsonPropertyName("mobilePhone")]
135
+ public string? MobilePhone { get; init; }
136
+ }
137
+
138
+ // Models/Asaas/CreatePaymentRequest.cs
139
+ public record CreatePaymentRequest
140
+ {
141
+ [JsonPropertyName("customer")]
142
+ public required string Customer { get; init; } // Asaas customer ID
143
+
144
+ [JsonPropertyName("billingType")]
145
+ public required string BillingType { get; init; } // BOLETO, PIX, CREDIT_CARD
146
+
147
+ [JsonPropertyName("value")]
148
+ public required decimal Value { get; init; }
149
+
150
+ [JsonPropertyName("dueDate")]
151
+ public required string DueDate { get; init; } // yyyy-MM-dd
152
+
153
+ [JsonPropertyName("description")]
154
+ public string? Description { get; init; }
155
+
156
+ [JsonPropertyName("externalReference")]
157
+ public string? ExternalReference { get; init; } // Seu ID interno
158
+ }
159
+
160
+ // Models/Asaas/AsaasPayment.cs
161
+ public record AsaasPayment
162
+ {
163
+ [JsonPropertyName("id")]
164
+ public string Id { get; init; } = "";
165
+
166
+ [JsonPropertyName("customer")]
167
+ public string Customer { get; init; } = "";
168
+
169
+ [JsonPropertyName("value")]
170
+ public decimal Value { get; init; }
171
+
172
+ [JsonPropertyName("status")]
173
+ public string Status { get; init; } = ""; // PENDING, RECEIVED, CONFIRMED, OVERDUE...
174
+
175
+ [JsonPropertyName("billingType")]
176
+ public string BillingType { get; init; } = "";
177
+
178
+ [JsonPropertyName("invoiceUrl")]
179
+ public string? InvoiceUrl { get; init; }
180
+
181
+ [JsonPropertyName("bankSlipUrl")]
182
+ public string? BankSlipUrl { get; init; } // URL do boleto
183
+
184
+ [JsonPropertyName("pixQrCode")]
185
+ public AsaasPixQrCode? PixQrCode { get; init; }
186
+ }
187
+
188
+ public record AsaasPixQrCode
189
+ {
190
+ [JsonPropertyName("encodedImage")]
191
+ public string? EncodedImage { get; init; } // Base64 QR Code
192
+
193
+ [JsonPropertyName("payload")]
194
+ public string? Payload { get; init; } // PIX copia-e-cola
195
+ }
196
+ ```
197
+
198
+ ## Webhooks
199
+
200
+ ```csharp
201
+ // Controllers/AsaasWebhookController.cs
202
+ [ApiController]
203
+ [Route("api/webhooks/asaas")]
204
+ public class AsaasWebhookController : ControllerBase
205
+ {
206
+ private readonly IPaymentService _paymentService;
207
+ private readonly ILogger<AsaasWebhookController> _logger;
208
+
209
+ public AsaasWebhookController(
210
+ IPaymentService paymentService,
211
+ ILogger<AsaasWebhookController> logger)
212
+ {
213
+ _paymentService = paymentService;
214
+ _logger = logger;
215
+ }
216
+
217
+ [HttpPost]
218
+ public async Task<IActionResult> HandleWebhook([FromBody] AsaasWebhookPayload payload)
219
+ {
220
+ _logger.LogInformation("Received Asaas webhook: {Event} for payment {PaymentId}",
221
+ payload.Event, payload.Payment?.Id);
222
+
223
+ try
224
+ {
225
+ switch (payload.Event)
226
+ {
227
+ case "PAYMENT_CONFIRMED":
228
+ case "PAYMENT_RECEIVED":
229
+ await _paymentService.ConfirmPaymentAsync(payload.Payment!.Id);
230
+ break;
231
+
232
+ case "PAYMENT_OVERDUE":
233
+ await _paymentService.MarkOverdueAsync(payload.Payment!.Id);
234
+ break;
235
+
236
+ case "PAYMENT_REFUNDED":
237
+ await _paymentService.RefundPaymentAsync(payload.Payment!.Id);
238
+ break;
239
+
240
+ default:
241
+ _logger.LogWarning("Unhandled webhook event: {Event}", payload.Event);
242
+ break;
243
+ }
244
+
245
+ return Ok();
246
+ }
247
+ catch (Exception ex)
248
+ {
249
+ _logger.LogError(ex, "Error processing webhook");
250
+ return StatusCode(500);
251
+ }
252
+ }
253
+ }
254
+
255
+ public record AsaasWebhookPayload
256
+ {
257
+ [JsonPropertyName("event")]
258
+ public string Event { get; init; } = "";
259
+
260
+ [JsonPropertyName("payment")]
261
+ public AsaasPayment? Payment { get; init; }
262
+ }
263
+ ```
264
+
265
+ ## Assinaturas Recorrentes
266
+
267
+ ```csharp
268
+ // Models/Asaas/CreateSubscriptionRequest.cs
269
+ public record CreateSubscriptionRequest
270
+ {
271
+ [JsonPropertyName("customer")]
272
+ public required string Customer { get; init; }
273
+
274
+ [JsonPropertyName("billingType")]
275
+ public required string BillingType { get; init; }
276
+
277
+ [JsonPropertyName("value")]
278
+ public required decimal Value { get; init; }
279
+
280
+ [JsonPropertyName("nextDueDate")]
281
+ public required string NextDueDate { get; init; }
282
+
283
+ [JsonPropertyName("cycle")]
284
+ public required string Cycle { get; init; } // MONTHLY, WEEKLY, BIWEEKLY, YEARLY
285
+
286
+ [JsonPropertyName("description")]
287
+ public string? Description { get; init; }
288
+
289
+ [JsonPropertyName("externalReference")]
290
+ public string? ExternalReference { get; init; }
291
+ }
292
+
293
+ // Criar assinatura
294
+ var subscription = await _asaasClient.CreateSubscriptionAsync(new()
295
+ {
296
+ Customer = customerId,
297
+ BillingType = "PIX",
298
+ Value = 49.90m,
299
+ NextDueDate = DateTime.Today.AddDays(1).ToString("yyyy-MM-dd"),
300
+ Cycle = "MONTHLY",
301
+ Description = "Plano Pro",
302
+ ExternalReference = $"plan_{planId}"
303
+ });
304
+ ```
305
+
306
+ ## Ambientes
307
+
308
+ | Ambiente | Base URL |
309
+ |----------|----------|
310
+ | Sandbox | `https://sandbox.asaas.com/api/v3` |
311
+ | Produção | `https://www.asaas.com/api/v3` |
312
+
313
+ ## Documentação de Referência
314
+
315
+ - [Asaas API Documentation](https://docs.asaas.com/)
316
+ - [Webhooks](https://docs.asaas.com/reference/webhooks)
317
+ - [Cobranças](https://docs.asaas.com/reference/criar-nova-cobranca)
318
+ - [Assinaturas](https://docs.asaas.com/reference/criar-nova-assinatura)
319
+
320
+ ## Checklist de Integração
321
+
322
+ - [ ] API Key configurada (não hardcoded)
323
+ - [ ] HttpClient com retry policy (Polly)
324
+ - [ ] Webhook endpoint configurado
325
+ - [ ] Validação de webhook signature
326
+ - [ ] Logs estruturados
327
+ - [ ] Tratamento de erros Asaas
328
+ - [ ] Testes com sandbox
329
+ - [ ] ExternalReference para reconciliação
330
+
331
+ ---
332
+
333
+ *MORPH-SPEC by Polymorphism Tech*
@@ -0,0 +1,309 @@
1
+ # Azure Identity (Microsoft Identity)
2
+
3
+ Especialista em autenticação com Microsoft Identity Platform para aplicações .NET/Blazor.
4
+
5
+ ## Responsabilidades
6
+
7
+ 1. **Configurar Microsoft Identity** em projetos .NET
8
+ 2. **Implementar autenticação** com Azure AD / Entra ID
9
+ 3. **Gerenciar tokens** e autorização
10
+ 4. **Integrar com APIs** protegidas
11
+
12
+ ## Triggers
13
+
14
+ Keywords: `identity`, `entra`, `azure ad`, `microsoft auth`, `msal`, `oauth`, `oidc`, `microsoft identity`
15
+
16
+ ## Sobre Microsoft Identity
17
+
18
+ - **Plataforma oficial** da Microsoft para autenticação
19
+ - **Suporta**: Azure AD, Microsoft accounts, B2C
20
+ - **SDK nativo**: Microsoft.Identity.Web
21
+ - **Ideal para**: Enterprise, Azure-first, Microsoft 365
22
+
23
+ ## Instalação
24
+
25
+ ```bash
26
+ dotnet add package Microsoft.Identity.Web
27
+ dotnet add package Microsoft.Identity.Web.UI # Para Blazor
28
+ ```
29
+
30
+ ## Configuração Básica
31
+
32
+ ```csharp
33
+ // appsettings.json
34
+ {
35
+ "AzureAd": {
36
+ "Instance": "https://login.microsoftonline.com/",
37
+ "Domain": "yourdomain.onmicrosoft.com",
38
+ "TenantId": "your-tenant-id",
39
+ "ClientId": "your-client-id",
40
+ "ClientSecret": "${AZURE_AD_CLIENT_SECRET}",
41
+ "CallbackPath": "/signin-oidc"
42
+ }
43
+ }
44
+
45
+ // Program.cs
46
+ builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
47
+ .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
48
+
49
+ builder.Services.AddControllersWithViews()
50
+ .AddMicrosoftIdentityUI();
51
+
52
+ builder.Services.AddAuthorization();
53
+
54
+ // Pipeline
55
+ app.UseAuthentication();
56
+ app.UseAuthorization();
57
+ ```
58
+
59
+ ## Blazor Server
60
+
61
+ ```csharp
62
+ // Program.cs
63
+ builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
64
+ .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
65
+
66
+ builder.Services.AddControllersWithViews()
67
+ .AddMicrosoftIdentityUI();
68
+
69
+ builder.Services.AddRazorPages();
70
+ builder.Services.AddServerSideBlazor()
71
+ .AddMicrosoftIdentityConsentHandler();
72
+
73
+ // App.razor
74
+ <CascadingAuthenticationState>
75
+ <Router AppAssembly="@typeof(App).Assembly">
76
+ <Found Context="routeData">
77
+ <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
78
+ <NotAuthorized>
79
+ @if (!context.User.Identity?.IsAuthenticated ?? true)
80
+ {
81
+ <RedirectToLogin />
82
+ }
83
+ else
84
+ {
85
+ <p>Você não tem permissão para acessar este recurso.</p>
86
+ }
87
+ </NotAuthorized>
88
+ </AuthorizeRouteView>
89
+ </Found>
90
+ </Router>
91
+ </CascadingAuthenticationState>
92
+
93
+ // Components/RedirectToLogin.razor
94
+ @inject NavigationManager Navigation
95
+
96
+ @code {
97
+ protected override void OnInitialized()
98
+ {
99
+ var returnUrl = Uri.EscapeDataString(Navigation.Uri);
100
+ Navigation.NavigateTo($"MicrosoftIdentity/Account/SignIn?redirectUri={returnUrl}", forceLoad: true);
101
+ }
102
+ }
103
+ ```
104
+
105
+ ## Páginas Protegidas
106
+
107
+ ```razor
108
+ @* Pages/Secure.razor *@
109
+ @page "/secure"
110
+ @attribute [Authorize]
111
+
112
+ <h1>Área Protegida</h1>
113
+
114
+ <AuthorizeView>
115
+ <Authorized>
116
+ <p>Bem-vindo, @context.User.Identity?.Name!</p>
117
+ <p>Email: @context.User.FindFirst("preferred_username")?.Value</p>
118
+ </Authorized>
119
+ </AuthorizeView>
120
+
121
+ @* Por role *@
122
+ <AuthorizeView Roles="Admin">
123
+ <Authorized>
124
+ <AdminPanel />
125
+ </Authorized>
126
+ </AuthorizeView>
127
+
128
+ @* Por policy *@
129
+ <AuthorizeView Policy="RequireManagerRole">
130
+ <Authorized>
131
+ <ManagerDashboard />
132
+ </Authorized>
133
+ </AuthorizeView>
134
+ ```
135
+
136
+ ## Proteger API
137
+
138
+ ```csharp
139
+ // Program.cs para API
140
+ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
141
+ .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
142
+
143
+ builder.Services.AddAuthorization();
144
+
145
+ // Controller
146
+ [ApiController]
147
+ [Route("api/[controller]")]
148
+ [Authorize]
149
+ public class ProfileController : ControllerBase
150
+ {
151
+ [HttpGet]
152
+ public IActionResult GetProfile()
153
+ {
154
+ return Ok(new
155
+ {
156
+ UserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value,
157
+ Name = User.Identity?.Name,
158
+ Email = User.FindFirst("preferred_username")?.Value
159
+ });
160
+ }
161
+
162
+ [HttpGet("admin")]
163
+ [Authorize(Roles = "Admin")]
164
+ public IActionResult AdminOnly()
165
+ {
166
+ return Ok("Admin access");
167
+ }
168
+ }
169
+ ```
170
+
171
+ ## Chamar APIs Protegidas (Downstream APIs)
172
+
173
+ ```csharp
174
+ // Program.cs
175
+ builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
176
+ .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
177
+ .EnableTokenAcquisitionToCallDownstreamApi()
178
+ .AddMicrosoftGraph(builder.Configuration.GetSection("Graph"))
179
+ .AddInMemoryTokenCaches();
180
+
181
+ // Service
182
+ public class ProfileService
183
+ {
184
+ private readonly GraphServiceClient _graphClient;
185
+
186
+ public ProfileService(GraphServiceClient graphClient)
187
+ {
188
+ _graphClient = graphClient;
189
+ }
190
+
191
+ public async Task<User> GetCurrentUserAsync()
192
+ {
193
+ return await _graphClient.Me.GetAsync();
194
+ }
195
+
196
+ public async Task<byte[]?> GetProfilePhotoAsync()
197
+ {
198
+ try
199
+ {
200
+ var photoStream = await _graphClient.Me.Photo.Content.GetAsync();
201
+ if (photoStream is null) return null;
202
+
203
+ using var memoryStream = new MemoryStream();
204
+ await photoStream.CopyToAsync(memoryStream);
205
+ return memoryStream.ToArray();
206
+ }
207
+ catch
208
+ {
209
+ return null;
210
+ }
211
+ }
212
+ }
213
+ ```
214
+
215
+ ## Authorization Policies
216
+
217
+ ```csharp
218
+ // Program.cs
219
+ builder.Services.AddAuthorization(options =>
220
+ {
221
+ options.AddPolicy("RequireAdmin", policy =>
222
+ policy.RequireRole("Admin"));
223
+
224
+ options.AddPolicy("RequireManager", policy =>
225
+ policy.RequireAssertion(context =>
226
+ context.User.IsInRole("Admin") ||
227
+ context.User.IsInRole("Manager")));
228
+
229
+ options.AddPolicy("RequireVerifiedEmail", policy =>
230
+ policy.RequireClaim("email_verified", "true"));
231
+ });
232
+ ```
233
+
234
+ ## Multi-tenant
235
+
236
+ ```csharp
237
+ // appsettings.json para multi-tenant
238
+ {
239
+ "AzureAd": {
240
+ "Instance": "https://login.microsoftonline.com/",
241
+ "TenantId": "common", // ou "organizations" para apenas work accounts
242
+ "ClientId": "your-client-id",
243
+ "ClientSecret": "${AZURE_AD_CLIENT_SECRET}"
244
+ }
245
+ }
246
+
247
+ // Validar tenant
248
+ builder.Services.Configure<OpenIdConnectOptions>(
249
+ OpenIdConnectDefaults.AuthenticationScheme,
250
+ options =>
251
+ {
252
+ options.TokenValidationParameters.IssuerValidator = (issuer, token, parameters) =>
253
+ {
254
+ // Validar que o tenant é permitido
255
+ var allowedTenants = new[] { "tenant-id-1", "tenant-id-2" };
256
+ var tenantId = issuer.Split('/')[3];
257
+
258
+ if (!allowedTenants.Contains(tenantId))
259
+ throw new SecurityTokenInvalidIssuerException("Tenant not allowed");
260
+
261
+ return issuer;
262
+ };
263
+ });
264
+ ```
265
+
266
+ ## Azure AD B2C
267
+
268
+ ```csharp
269
+ // appsettings.json
270
+ {
271
+ "AzureAdB2C": {
272
+ "Instance": "https://yourtenant.b2clogin.com",
273
+ "Domain": "yourtenant.onmicrosoft.com",
274
+ "TenantId": "your-tenant-id",
275
+ "ClientId": "your-client-id",
276
+ "SignUpSignInPolicyId": "B2C_1_signupsignin",
277
+ "ResetPasswordPolicyId": "B2C_1_passwordreset",
278
+ "EditProfilePolicyId": "B2C_1_editprofile"
279
+ }
280
+ }
281
+
282
+ // Program.cs
283
+ builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
284
+ .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C"));
285
+ ```
286
+
287
+ ## Documentação de Referência
288
+
289
+ - [Microsoft Identity Platform](https://learn.microsoft.com/en-us/entra/identity-platform/)
290
+ - [Microsoft.Identity.Web](https://learn.microsoft.com/en-us/entra/msal/dotnet/)
291
+ - [Blazor + Azure AD](https://learn.microsoft.com/en-us/aspnet/core/blazor/security/server/)
292
+ - [Microsoft Graph](https://learn.microsoft.com/en-us/graph/overview)
293
+ - [Azure AD B2C](https://learn.microsoft.com/en-us/azure/active-directory-b2c/)
294
+
295
+ ## Checklist de Integração
296
+
297
+ - [ ] App registrado no Azure Portal
298
+ - [ ] Client ID e Tenant ID configurados
299
+ - [ ] Client Secret no Key Vault
300
+ - [ ] Redirect URIs configurados
301
+ - [ ] API permissions definidas
302
+ - [ ] Token caching configurado
303
+ - [ ] Authorization policies criadas
304
+ - [ ] Logout flow implementado
305
+ - [ ] Error handling para tokens expirados
306
+
307
+ ---
308
+
309
+ *MORPH-SPEC by Polymorphism Tech*