Skip to main content

Lazy Authentication — Como o Sistema Funciona Apesar do Bug de Ordering

Data: 2026-07-08


1. O Mecanismo de Lazy Auth

UseAuthorization() (chamado em ApiConfig.UseApiConfiguration()) registra o AuthorizationMiddleware do ASP.NET Core. Este middleware, ao encontrar um endpoint com metadata [Authorize], dispara autenticação sob demanda — mesmo que UseAuthentication() nunca tenha executado.

Fluxo detalhado

1. Request chega
2. UseRouting() → seleciona endpoint com metadata [Authorize(JwtBearer)]
3. UseAuthorization() → AuthorizationMiddleware é invocado
├── PolicyEvaluator.AuthenticateAsync()
│ └── AuthenticationService.AuthenticateAsync("Bearer")
│ └── JwtBearerHandler.HandleAuthenticateAsync()
│ ├── Lê header Authorization: Bearer {token}
│ ├── Valida assinatura (HMAC-SHA256)
│ ├── Valida expiração (ValidateLifetime = true)
│ └── Cria ClaimsPrincipal com claims:
│ ├── sub → ClaimTypes.NameIdentifier
│ ├── schema → "schema"
│ ├── profile → "profile"
│ └── unique_name

│ → HttpContext.User populado ✅

└── PolicyEvaluator.AuthorizeAsync()
└── Verifica policies do [Authorize] contra User
└── OK → continua pipeline
4. UseEndpoints() → controller executa
└── GetUserId() → _context.HttpContext.User.Claims.First(x => x.Type == ClaimTypes.NameIdentifier) ✅
└── GetSchemaName() → http.User?.FindFirst("schema") ✅

Código fonte relevante

AuthenticationConfig.cs — JWT handler registrado:

// coezzion-nuget-authentication/src/Coezzion.Authentication/Configuration/AuthenticationConfig.cs
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
SaveSigninToken = true,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = null // ← carregado lazy no OnMessageReceived
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// Carrega secret da config (AWS Secrets Manager)
var secret = context.HttpContext.RequestServices
.GetRequiredService<IConfiguration>()
.GetSection("TokenConfigurations")["SecretJWTKey"];
options.TokenValidationParameters.IssuerSigningKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
return Task.CompletedTask;
}
};
});

2. Por Que Funciona com [Authorize]

┌──────────────────────────────────────────────────────────┐
│ Pipeline: com [Authorize] │
├──────────────────────────────────────────────────────────┤
│ UseRouting() → seleciona endpoint + metadata │
│ UseCors() │
│ UseAuthorization() → vê [Authorize] → dispara lazy auth │
│ │ │
│ ├── JwtBearerHandler valida token │
│ ├── HttpContext.User = { schema, profile, sub, ... } │
│ └── Authorization check OK │
│ │
│ SerilLogMiddleware │
│ UseEndpoints() → Controller.GetUserId() ✅ │
│ → Controller.GetSchemaName() ✅ │
│ (FindFirst("schema") → "arezzo") │
└──────────────────────────────────────────────────────────┘

3. Por Que FALHA sem [Authorize]

┌──────────────────────────────────────────────────────────┐
│ Pipeline: sem [Authorize] │
├──────────────────────────────────────────────────────────┤
│ UseRouting() → seleciona endpoint SEM metadata │
│ UseCors() │
│ UseAuthorization() → NÃO vê [Authorize] │
│ │ │
│ └── NÃO dispara lazy auth │
│ HttpContext.User = VAZIO (unauthenticated) │
│ │
│ SerilLogMiddleware │
│ UseEndpoints() → Controller executa │
│ → GetUserId() ❌ (InvalidOperationEx) │
│ → GetSchemaName() │
│ FindFirst("schema") → null │
│ Cai no header api-company-target ✅ │
│ → header DO CLIENTE controla tenant │
└──────────────────────────────────────────────────────────┘

4. GetUserId() — Por Que Nunca Falha

UserProvider.GetUserId():

// coezzion-nuget-common/src/Coezzion.Common/Providers/UserProvider.cs:25-32
public int GetUserId()
{
var userId = _context.HttpContext.User.Claims
.First(x => x.Type == ClaimTypes.NameIdentifier)
.Value;
return int.Parse(userId);
}

Este método lança exceção se HttpContext.User não tiver claims. Mas ele NUNCA é chamado em endpoints sem [Authorize]. Todos os call sites estão em endpoints protegidos por [Authorize(JwtBearer)]:

Cart service (exemplo):

  • AttendanceController.GetCartByStoreId[Authorize(JwtBearer)] no controller
  • AttendanceController.FindDetailWithStockById — mesmo controller
  • AttendanceController.DeleteById — mesmo controller

Convenção, não enforcement: o código depende da disciplina do desenvolvedor de só chamar GetUserId() em endpoints autenticados. Não há validação em runtime.


5. GetSchemaName() — Por Que o Header Vence

UserProvider.GetSchemaName():

public string GetSchemaName()
{
// 1) x-api-key venda_ai → "arezzo" (hardcoded)
if (apiKeyFromHeader == vendaAiKeyFromConfig) return "arezzo";

// 2) JWT claim "schema" → NUNCA ENCONTRADO (User vazio sem [Authorize])
var schemaClaim = http.User?.FindFirst("schema");
if (schemaClaim != null && !string.IsNullOrEmpty(schemaClaim.Value))
return schemaClaim.Value; // ← NÃO EXECUTA

// 3) Header api-company-target → SEMPRE VENCE
if (http.Request.Headers.TryGetValue("api-company-target", out StringValues headerSchema)
&& headerSchema.Any())
return headerSchema.ToString(); // ← EXECUTA

// 4) Default
return _schemaNameScopedWrapper.SchemaName;
}

Cenários:

Endpoint tem [Authorize]?JWT enviado?FindFirst("schema")Schema usado
SimSim"arezzo" (do JWT)JWT claim ✅
NãoSimnull (User vazio)Header ❌
NãoNãonull (User vazio)Header ❌

6. O Que o Fix de #30 Corrige

Após mover UseAuthentication() para ANTES de UseEndpoints():

┌──────────────────────────────────────────────────────────┐
│ Pipeline: DEPOIS do fix (com ou sem [Authorize]) │
├──────────────────────────────────────────────────────────┤
│ UseSwagger() │
│ UseAuthentication() → JwtBearerHandler SEMPRE executa │
│ │ │
│ └── HttpContext.User SEMPRE populado ✅ │
│ │
│ UseAuthorization() (1ª) → [Authorize] OK │
│ UseRouting() │
│ UseCors() │
│ UseAuthorization() (2ª, duplicado → no-op) │
│ SerilLogMiddleware │
│ UseEndpoints() → Controller executa │
│ → GetUserId() ✅ │
│ → GetSchemaName() │
│ FindFirst("schema") → "arezzo" ✅ │
│ (header IGNORADO) │
└──────────────────────────────────────────────────────────┘

HttpContext.User populado para todos os endpoints, com ou sem [Authorize]. Claim schema sempre disponível → prioridade 2 vence prioridade 3 → header do cliente nunca controla o tenant.