Skip to main content

Plano de Fix — #30 Header api-company-target Sobrescreve Claim JWT schema

Data: 2026-07-08 Severidade: Alto Vetor: Middleware pipeline — UseAuthentication() registrado DEPOIS de UseEndpoints() (terminal)


1. Resumo

O middleware UseAuthentication() é registrado DEPOIS de UseEndpoints() em 4 serviços. UseEndpoints(MapControllers) é middleware terminal — não chama o próximo. Resultado: HttpContext.User nunca é populado pelo pipeline de autenticação.

Consequência: em endpoints SEM [Authorize], UserProvider.GetSchemaName() não encontra claim schema no JWT (User vazio) → cai no header api-company-target → header do cliente controla o tenant.

Evidência do pentest:

GET /api/payment/profile-fraud
Authorization: Bearer eyJ...schema:"arezzo"...
api-company-target: schutz

→ 503 "Não foi possível efetuar a conexão com o schema schutz.CustomerProfileFraud"

Servidor tentou schema schutz (do header), ignorou arezzo (do JWT).


2. Causa Raiz

2.1 UseEndpoints() é middleware terminal

Quando uma rota casa com um controller, o EndpointMiddleware executa o controller e envia a resposta. Não invoca _next. Middleware registrado depois nunca executa para requisições que batem em controllers.

Documentação Microsoft: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/

"When a middleware short-circuits, it's called a terminal middleware because it prevents further middleware from processing the request."

Ordem obrigatória documentada:

UseRouting → UseCors → UseAuthentication → UseAuthorization → UseEndpoints(MapControllers)

2.2 Ordem real nos 4 serviços

Startup.Configure() (todos os serviços):

// Checkout Startup.cs:70-74
app.UseApiConfiguration(env); // contém UseAuthZ + UseEndpoints
app.UseSwaggerConfiguration(env, Configuration);
app.UseAuthenticationConfiguration(); // contém UseAuthN + UseAuthZ ← NUNCA EXECUTA

ApiConfig.UseApiConfiguration() (checkout L61-91):

app.UseRouting(); // L75
app.UseCors("Total"); // L77
app.UseAuthorization(); // L79 ← ANTES de UseAuthN
app.UseMiddleware<SerilLogMiddleware>(); // L81
app.UseEndpoints(endpoints => // L83 ← TERMINAL
{
endpoints.MapControllers();
});

AuthenticationConfig.UseAuthenticationConfiguration() (nuget L81-88):

app.UseAuthentication(); // L83 ← DEPOIS do terminal → NUNCA executa
app.UseAuthorization(); // L85 ← DEPOIS do terminal → NUNCA executa (duplicado)

2.3 Pipeline real em runtime

UseRouting() ✅
UseCors() ✅
UseAuthorization() ✅ (mas User ainda não populado)
SerilLogMiddleware ✅
UseEndpoints() ✅ → executa controller → TERMINAL
--- NADA abaixo executa para requests em controllers ---
UseAuthentication() ❌ NUNCA
UseAuthorization() ❌ NUNCA (duplicado)

2.4 Por que o sistema "funciona" apesar do bug

UseAuthorization() (L79, que RODA) implementa lazy authentication: quando encontra um endpoint com [Authorize], chama automaticamente JwtBearerHandler.HandleAuthenticateAsync(), que valida o token e popula HttpContext.User.

Isso mascara o bug para endpoints COM [Authorize]. Mas endpoints SEM [Authorize] nunca disparam lazy auth → HttpContext.User permanece vazio → claim schema sempre null → UserProvider.GetSchemaName() cai no header (prioridade 3).

Fluxo com [Authorize]:

Request → UseRouting() seleciona endpoint com [Authorize]
→ UseAuthorization() vê metadata → dispara lazy auth
→ JwtBearerHandler valida token → User populado (schema, profile, sub)
→ Authorization OK
→ UseEndpoints() → controller executa

Fluxo sem [Authorize] (vulnerável):

Request → UseRouting() seleciona endpoint SEM [Authorize]
→ UseAuthorization() não vê metadata → NÃO dispara lazy auth
→ User VAZIO
→ UseEndpoints() → controller executa
→ GetSchemaName(): FindFirst("schema") = null → header vence ❌

3. Decisões de Design

#DecisãoEscolhaMotivo
1AbordagemMover UseAuthNConf() antes de UseApiConf() (Abordagem A)1 linha por serviço, sem alterar lógica interna dos métodos
2Escopo4 serviços do pentest (checkout, cart, product, store)Demais 13+ serviços seguem mesmo padrão — card futuro
3Duplicata UseAuthZManter (remover depois)Segunda chamada é no-op com User já autenticado. Remoção é refactor menor, card separado
4Ordem #08 vs #30#08 primeiro (já funciona via lazy auth), #30 depois#30 não é bloqueante para #08. Fix de #08 + #30 = defesa completa
5Swagger antes de authManterSwagger serve assets estáticos, não precisa de auth

4. Fix: Reordenar Startup.Configure()

4.1 Checkout

Arquivo: coezzion-service-checkout/src/Checkout.API/Startup.cs:68-76

// ANTES:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IInitializeService initializeService)
{
app.UseApiConfiguration(env);
app.UseSwaggerConfiguration(env, Configuration);
app.UseAuthenticationConfiguration(); // ← DEPOIS de UseEndpoints

// DEPOIS:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IInitializeService initializeService)
{
app.UseSwaggerConfiguration(env, Configuration);
app.UseAuthenticationConfiguration(); // ← UseAuthN + UseAuthZ PRIMEIRO
app.UseApiConfiguration(env); // ← UseAuthZ + UseEndpoints DEPOIS

4.2 Cart

Arquivo: coezzion-service-cart/src/Cart.API/Startup.cs:57-64

// ANTES:
app.UseApiConfiguration(env, Configuration);
app.UseSwaggerConfiguration(env);
app.UseAuthenticationConfiguration();

// DEPOIS:
app.UseSwaggerConfiguration(env);
app.UseAuthenticationConfiguration();
app.UseApiConfiguration(env, Configuration);

4.3 Product

Arquivo: coezzion-service-product/src/Product.API/Startup.cs:63-72

// ANTES:
app.UseApiConfiguration(env, Configuration);
app.UseSwaggerConfiguration(env, Configuration);
app.UseAuthenticationConfiguration();

// DEPOIS:
app.UseSwaggerConfiguration(env, Configuration);
app.UseAuthenticationConfiguration();
app.UseApiConfiguration(env, Configuration);

4.4 Store

Arquivo: coezzion-service-store/src/Store.API/Startup.cs:57-62

// ANTES:
app.UseApiConfiguration(env, Configuration);
app.UseAuthenticationConfiguration();

// DEPOIS:
app.UseAuthenticationConfiguration();
app.UseApiConfiguration(env, Configuration);

5. Pipeline Resultante

DEPOIS:
UseSwagger() ← static files
UseAuthN() ← ✅ JwtBearerHandler popula HttpContext.User
UseAuthZ() ← ✅ [Authorize] verificado
UseRouting()
UseCors()
UseAuthZ() ← duplicado (no-op, User já autenticado)
SerilLogMiddleware
UseEndpoints() ← TERMINAL

HttpContext.User populado para TODOS os endpoints, com ou sem [Authorize]. Claim schema sempre disponível → GetSchemaName() prioridade 2 vence prioridade 3.


6. Verificação de Segurança

#TesteEsperado
1GET /api/payment/profile-fraud com JWT schema: arezzo + header api-company-target: schutzSchema usado: arezzo (JWT claim vence)
2GET /api/payment/profile-fraud com JWT schema: arezzo sem header api-company-targetSchema usado: arezzo (JWT claim)
3GET /api/payment/profile-fraud sem JWT + header api-company-target: arezzo401 (endpoint exige [Authorize] via #08)
4Qualquer endpoint com [Authorize] + JWT válidoGetUserId() funciona, GetSchemaName() retorna claim
5Qualquer endpoint sem [Authorize] + JWT válidoHttpContext.User populado — claim schema disponível
6[Authorize] + JWT inválido/expirado401 Unauthorized

7. Fora do Escopo

ItemMotivoQuando
Remover UseAuthZ duplicado do ApiConfig.csNo-op inofensivo, refactor cosméticoCard separado
Corrigir ordering nos 13+ serviços restantesEscopo do pentest cobre apenas 4Card futuro batch
SignalR hub auth (/hubs/*)UseAuthN deve rodar no connection negotiate — afetado pelo mesmo bugCard #29
Health check authMapHealthChecks("/check") sem auth — não afetado
Background jobs / event handlersNão passam pelo pipeline HTTP — schema via SchemaNameScopedWrapperOK

8. Relação com Outros Cards

CardRelação
#08Adiciona [Authorize] nos endpoints de payment. Lazy auth já funciona. Fix de #30 garante auth para TODOS os endpoints, não só os com [Authorize].
#07Schema whitelist ("arezzo") no UserProvider. Com #30, claim JWT sempre tem precedência. Whitelist é defense-in-depth.
#29SignalR de PROD aceita JWT de QA. Mesma causa raiz: UseAuthN() não executa no negotiate.