Pipeline Middleware Reference — ASP.NET Core
Data: 2026-07-09
Escopo: UseRouting, UseAuthentication, UseAuthorization, UseEndpoints, AddSecondaryAuthentication
1. Visão Geral
O pipeline de middleware do ASP.NET Core é uma cadeia de delegates executados em sequência. Cada middleware decide se passa o request para o próximo (_next) ou termina o pipeline (short-circuit).
Ordem Obrigatória (documentação Microsoft)
UseRouting → UseCors → UseAuthentication → UseAuthorization → UseEndpoints
Fonte: Microsoft Learn — Middleware order — "CORS Middleware, Authentication Middleware, and Authorization Middleware must appear in the order shown."
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/#middleware-order
Terminal Middleware
Middleware que não invoca _next é chamado de terminal middleware — previne que middlewares registrados depois dele executem.
Fonte: Microsoft Learn — Middleware — "When a middleware short-circuits, it's called a terminal middleware because it prevents further middleware from processing the request."
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/
UseEndpoints(MapControllers) é terminal quando uma rota casa com um controller.
2. UseRouting
Responsabilidade
Seleciona o endpoint que melhor casa com o request (URL path, HTTP method, constraints). Popula metadata no HttpContext.
Funcionamento
- Examina todos os endpoints registrados (controllers, health checks, etc.)
- Aplica URL matching, constraints,
MatcherPolicyinstances - Seleciona o melhor match via
EndpointSelector - Define
HttpContext.GetEndpoint()→ disponível para middlewares subsequentes - Popula
HttpRequest.RouteValuescom parâmetros da rota
Fonte: Microsoft Learn — Routing — "
UseRoutingadds route matching to the middleware pipeline. This middleware looks at the set of endpoints defined in the app, and selects the best match based on the request."https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing#routing-basics
Restrições
- Metadata do endpoint (
[Authorize],[AllowAnonymous], etc.) só está disponível depois deUseRouting - Antes de
UseRouting:HttpContext.GetEndpoint()=null - Depois de
UseRoutinge antes deUseEndpoints: endpoint disponível para inspeção porUseCors,UseAuthentication,UseAuthorization
Fonte: Microsoft Learn — Routing — "The endpoint is always null before
UseRoutingis called. If a match is found, the endpoint is non-null betweenUseRoutingandUseEndpoints."https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing#endpoints
3. UseAuthentication
Responsabilidade
Autentica o usuário usando o esquema de autenticação registrado (JWT Bearer, cookies, etc.). Popula HttpContext.User com um ClaimsPrincipal em caso de sucesso.
Funcionamento (código fonte)
// AuthenticationMiddleware.cs — dotnet/aspnetcore (MIT License)
// https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Core/src/AuthenticationMiddleware.cs
public async Task Invoke(HttpContext context)
{
// 1. IAuthenticationRequestHandler schemes — handle if applicable
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
{
var handler = await handlers.GetHandlerAsync(context, scheme.Name)
as IAuthenticationRequestHandler;
if (handler != null && await handler.HandleRequestAsync())
return; // ← único caso de short-circuit
}
// 2. Default authenticate scheme → AuthenticateAsync
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
if (result?.Principal != null)
context.User = result.Principal; // ← popula User
if (result?.Succeeded ?? false)
{
// Armazena AuthenticateResult no HttpContext.Features
var authFeatures = new AuthenticationFeatures(result);
context.Features.Set<IHttpAuthenticationFeature>(authFeatures);
context.Features.Set<IAuthenticateResultFeature>(authFeatures);
}
}
// 3. SEMPRE chama o próximo middleware
await _next(context); // ← NUNCA bloqueia por falha de auth
}
Comportamento com JWT Bearer (Coezzion)
┌─ JwtBearerHandler.HandleAuthenticateAsync() ─────────────┐
│ 1. OnMessageReceived: carrega IssuerSigningKey do AWS │
│ Secrets Manager (lazy, só no primeiro request) │
│ 2. Lê header Authorization: Bearer {token} │
│ 3. Valida assinatura (HMAC-SHA256) │
│ 4. Valida expiração (ValidateLifetime = true) │
│ 5. NÃO valida Issuer/Audience (ValidateIssuer = false, │
│ ValidateAudience = false) │
│ 6. Cria ClaimsPrincipal com claims: │
│ ├── sub → ClaimTypes.NameIdentifier │
│ ├── schema → "schema" │
│ ├── profile → "profile" │
│ └── unique_name │
└──────────────────────────────────────────────────────────┘
AuthenticationConfig.cs (Coezzion):
ValidateIssuer = falseValidateAudience = falseValidateLifetime = trueValidateIssuerSigningKey = true(HMAC-SHA256 com secret do AWS Secrets Manager)
Restrições e Side Effects
| Aspecto | Detalhe |
|---|---|
| Nunca rejeita request | Sempre chama _next(context) — sucesso, falha, ou sem token |
| Sem token | AuthenticateResult.NoResult() → HttpContext.User = anonymous |
| Token inválido/expirado | AuthenticateResult.Fail(...) → HttpContext.User = anonymous |
| Token válido | AuthenticateResult.Success → HttpContext.User = ClaimsPrincipal populado |
| OnMessageReceived lazy | IssuerSigningKey = null até o primeiro request disparar o evento |
| IAuthenticationRequestHandler | Único caso de short-circuit (ex: remote auth redirect) |
Fonte: Microsoft Learn — Authentication — "Authentication doesn't short-circuit unauthenticated requests. Although Authentication Middleware authenticates requests, authorization (and rejection) occurs only after MVC selects a specific controller and action."
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/
4. UseAuthorization
Responsabilidade
Autoriza o acesso ao endpoint baseado em políticas ([Authorize], [AllowAnonymous], políticas customizadas). Único middleware que pode rejeitar requests com 401/403.
Funcionamento (código fonte)
// AuthorizationMiddleware.cs — dotnet/aspnetcore (MIT License)
// https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs
public async Task Invoke(HttpContext context)
{
var endpoint = context.GetEndpoint();
// Tenta cached policy, senão combina metadata do endpoint
var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>()
?? Array.Empty<IAuthorizeData>();
policy = await AuthorizationPolicy.CombineAsync(_policyProvider,
authorizeData, policies);
// ⚠️ SE policy == null → endpoint NÃO tem [Authorize] → skip total
if (policy == null)
{
await _next(context); // ← request prossegue sem verificação
return;
}
// ⚠️ LAZY AUTH: AuthenticateAsync dispara JWT validation mesmo
// sem UseAuthentication ter executado antes
var authenticateResult = await policyEvaluator.AuthenticateAsync(policy, context);
// ⚠️ [AllowAnonymous]: roda auth (popula User) mas NÃO bloqueia
if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)
{
await _next(context);
return;
}
// Authorize: verifica políticas contra o User autenticado
var authorizeResult = await policyEvaluator.AuthorizeAsync(
policy, authenticateResult!, context, resource);
// Result handler: Challenge (401) ou Forbid (403)
await authorizationMiddlewareResultHandler.HandleAsync(
_next, context, policy, authorizeResult);
}
Fluxo Detalhado
┌─ AuthorizationMiddleware.Invoke() ─────────────────────────┐
│ │
│ 1. context.GetEndpoint() → extrai metadata │
│ │
│ 2. endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>() │
│ ├── Sem [Authorize] → authorizeData vazio │
│ │ └── policy = null → await _next(context) ✅ SKIP │
│ │ │
│ └── Com [Authorize] → combine policy │
│ │
│ 3. policy != null → PolicyEvaluator.AuthenticateAsync() │
│ └── LAZY AUTH: dispara JwtBearerHandler mesmo se │
│ UseAuthentication NÃO executou antes │
│ │
│ 4. IAllowAnonymous? → skip auth check, prossegue │
│ │
│ 5. PolicyEvaluator.AuthorizeAsync(policy, result, ctx) │
│ ├── Sucesso → _next(context) │
│ ├── Falha (não autenticado) → Challenge() → 401 │
│ └── Falha (autenticado, sem permissão) → Forbid() → 403 │
│ │
└────────────────────────────────────────────────────────────┘
Lazy Authentication
Mecanismo que permite que UseAuthorization funcione mesmo sem UseAuthentication ter executado antes:
UseAuthorization encontra [Authorize]
→ PolicyEvaluator.AuthenticateAsync("Bearer")
→ IAuthenticationService.AuthenticateAsync(context, "Bearer")
→ JwtBearerHandler.HandleAuthenticateAsync()
→ Valida token → HttpContext.User populado
Este é o mecanismo que mascara o bug de ordering do Coezzion: endpoints com [Authorize] funcionam porque o AuthorizationMiddleware dispara a autenticação lazy. Endpoints sem [Authorize] → policy == null → skip total → HttpContext.User permanece vazio.
FallbackPolicy
Configuração opcional que aplica uma política de autorização a todos os endpoints, incluindo os que não têm [Authorize]:
// Exemplo da documentação Microsoft — NÃO configurado no Coezzion
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
Fonte: Microsoft Learn — Secure data — "Setting the fallback authorization policy to require users to be authenticated protects newly added Razor Pages and controllers. Having authorization required by default is more secure than relying on new controllers and Razor Pages to include the
[Authorize]attribute."
No Coezzion: FallbackPolicy NÃO é configurada. Logo, endpoints sem [Authorize] não são bloqueados. Apenas endpoints com [Authorize] (ou com FallbackPolicy configurada, o que não é o caso) são verificados.
[AllowAnonymous]
Endpoint metadata que instrui o AuthorizationMiddleware a pular a verificação de autorização, mesmo quando há [Authorize] no controller ou FallbackPolicy configurada. O middleware ainda executa AuthenticateAsync() (popula HttpContext.User) mas não rejeita requests não autenticados.
Fonte: Microsoft Learn — Simple authorization — "Use the
[AllowAnonymous]attribute to allow access by non-authenticated users to individual actions."https://learn.microsoft.com/en-us/aspnet/core/security/authorization/simple
Restrições e Side Effects
| Aspecto | Detalhe |
|---|---|
Endpoint sem [Authorize] | policy == null → skip total → request prossegue |
Endpoint com [Authorize] | Lazy auth → valida token → 401 se inválido |
Endpoint com [AllowAnonymous] | Popula User (se token) mas NÃO bloqueia |
Sem FallbackPolicy | Apenas endpoints com [Authorize] são verificados |
Com FallbackPolicy | Todos endpoints exigem auth (exceto [AllowAnonymous]) |
Duplicata de UseAuthorization | Segunda chamada é no-op: AuthorizationMiddlewareInvokedWithEndpointKey no HttpContext.Items previne dupla execução |
5. UseEndpoints
Responsabilidade
Executa o delegate associado ao endpoint selecionado por UseRouting. É o ponto final do pipeline para requests que casam com uma rota.
Funcionamento
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/check"); // Health check endpoint
endpoints.MapControllers(); // Controller actions
});
MapControllers(): executa o controller e action selecionados porUseRoutingMapHealthChecks(): executa health checksMapRazorPages(): executa Razor PagesMapHub<T>(): SignalR hubs
Fonte: Microsoft Learn — Routing — "
UseEndpointsadds endpoint execution to the middleware pipeline. It runs the delegate associated with the selected endpoint."https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing#routing-basics
Terminal Middleware
UseEndpoints é terminal quando encontra um match:
Fonte: Microsoft Learn — Routing — "The
UseEndpointsmiddleware is terminal when a match is found. The middleware afterUseEndpointsexecute only when no match is found."https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing#endpoints
Request casa com /api/payment/profile-fraud
→ UseEndpoints(MapControllers) executa ProfileFraudController.Get()
→ Response enviada
→ NÃO chama _next
→ Middleware registrado DEPOIS de UseEndpoints NUNCA executa ❌
Restrições
| Aspecto | Detalhe |
|---|---|
| Terminal com match | Não chama _next → middleware depois dele nunca executa |
| Sem match | Chama _next → middleware depois dele executa (ex: 404 handler) |
| Ordem no pipeline | Deve ser o último middleware (depois de authN, authZ) |
| Registro de endpoints | Feito em ConfigureServices ou no WebApplication builder |
6. AddSecondaryAuthentication (Coezzion)
Responsabilidade
Registra um segundo esquema JWT (PaymentsScheme) para autenticação de requests de pagamento, com issuer/audience específicos.
Código
// coezzion-service-checkout/src/Checkout.API/Configuration/AuthenticationExtensions.cs
public static IServiceCollection AddSecondaryAuthentication(
this IServiceCollection services, IConfiguration configuration)
{
services.AddAuthentication()
.AddJwtBearer("PaymentsScheme", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true, // ← diferente do esquema default
ValidateAudience = true, // ← diferente do esquema default
ValidateLifetime = true,
ValidAudience = configuration["TokenConfigurations:PaymentsTokenAudience"],
ValidIssuer = configuration["TokenConfigurations:PaymentsTokenIssuer"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(configuration["TokenConfigurations:PaymentsTokenSecretKey"]))
};
});
return services;
}
Diferenças entre esquemas
| Parâmetro | Default (Bearer) | PaymentsScheme |
|---|---|---|
| ValidateIssuer | false | true |
| ValidateAudience | false | true |
| ValidateLifetime | true | true |
| IssuerSigningKey | AWS Secrets Manager (lazy) | appsettings.json |
| Uso | [Authorize(JwtBearer)] | [Authorize(AuthenticationSchemes = "PaymentsScheme")] |
Funcionamento no Pipeline
ConfigureServices:
services.AddAuthenticationConfiguration()
→ AddAuthentication("Bearer").AddJwtBearer(...) // esquema DEFAULT
services.AddSecondaryAuthentication(Configuration)
→ AddAuthentication().AddJwtBearer("PaymentsScheme") // esquema adicional
Pipeline:
UseAuthentication() → tenta autenticar com esquema DEFAULT ("Bearer")
→ JwtBearerHandler (default) valida token do header Authorization
UseAuthorization() → verifica metadata do endpoint
→ [Authorize] sem schemes → usa default ("Bearer")
→ [Authorize(AuthenticationSchemes = "PaymentsScheme")] → usa PaymentsScheme
7. Restrições de Ordenação
Ordem Documentada pela Microsoft
1. Exception/Error Handling (UseExceptionHandler, UseDeveloperExceptionPage)
2. HTTPS Redirection (UseHttpsRedirection)
3. Static Files (UseStaticFiles)
4. UseRouting() ← endpoint selection
5. UseCors() ← must be after UseRouting, before UseEndpoints
6. UseAuthentication() ← must be after UseRouting, before UseEndpoints
7. UseAuthorization() ← must be after UseAuthentication
8. UseEndpoints() ← TERMINAL — must be LAST
Fonte: Microsoft Learn — Middleware order — multiple versions document this exact sequence.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/#middleware-order
Regras Fundamentais
| Regra | Motivo |
|---|---|
UseRouting antes de UseAuthN/UseAuthZ | Metadata do endpoint ([Authorize]) só existe depois do routing |
UseAuthN antes de UseAuthZ | UseAuthZ precisa do HttpContext.User populado |
UseEndpoints por último | É terminal — middleware depois dele nunca executa para requests que casam com rota |
UseCors antes de UseEndpoints | CORS headers precisam ser aplicados antes da resposta |
Fonte: Microsoft Learn — Authentication — "When using endpoint routing, the call to
UseAuthenticationmust go: AfterUseRouting, so that route information is available for authentication decisions. BeforeUseEndpoints, so that users are authenticated before accessing the endpoints."https://learn.microsoft.com/en-us/aspnet/core/security/authentication/
8. Análise do Bug de Ordenação no Coezzion
Pipeline Real (Bug)
// Startup.Configure() — todos os 4 serviços do pentest
app.UseApiConfiguration(env); // ← contém UseRouting, UseAuthZ, UseEndpoints
app.UseSwaggerConfiguration(env);
app.UseAuthenticationConfiguration(); // ← contém UseAuthN, UseAuthZ — NUNCA EXECUTA
// ApiConfig.UseApiConfiguration():
app.UseRouting(); // ✅
app.UseCors("Total"); // ✅
app.UseAuthorization(); // ✅ (mas HttpContext.User VAZIO)
app.UseMiddleware<SerilLogMiddleware>(); // ✅
app.UseEndpoints(endpoints => // ✅ ← TERMINAL
{
endpoints.MapControllers(); // match → executa → não chama _next
});
// ──── NADA abaixo executa ────
// app.UseAuthentication(); // ❌ NUNCA EXECUTA
// app.UseAuthorization(); // ❌ NUNCA EXECUTA (duplicado)
Como o Sistema "Funciona" Apesar do Bug
O UseAuthorization() em ApiConfig:79 encontra [Authorize] na metadata do endpoint e dispara lazy auth:
UseAuthorization (ApiConfig L79)
→ vê [Authorize(JwtBearer)] na metadata
→ PolicyEvaluator.AuthenticateAsync("Bearer")
→ JwtBearerHandler.HandleAuthenticateAsync()
→ Valida token → HttpContext.User = { schema, sub, profile }
Isso mascara o bug para endpoints com [Authorize]. Mas endpoints sem [Authorize]:
UseAuthorization (ApiConfig L79)
→ NÃO vê [Authorize] → policy = null → skip total
→ HttpContext.User permanece VAZIO
Consequência Explorada no Pentest
GET /api/payment/profile-fraud
Authorization: Bearer eyJ...schema:"arezzo"... ← JWT válido com tenant "arezzo"
api-company-target: schutz ← header malicioso
Pipeline:
UseAuthorization → sem [Authorize] no endpoint → lazy auth NÃO dispara
UseEndpoints → controller executa
→ GetSchemaName():
→ http.User.FindFirst("schema") = null (User VAZIO!)
→ cai no header api-company-target → "schutz" ❌
Resultado: servidor conecta ao schema do ATACANTE (schutz), não do JWT (arezzo)
Análise de Impacto: Fix vs Regressão
Pipeline corrigido:
UseAuthN → JwtBearerHandler SEMPRE executa → User populado (se token)
UseAuthZ → verifica endpoint metadata
UseEndpoints → executa controller
Endpoints sem [Authorize]:
- Sem FallbackPolicy → policy = null → prossegue SEM bloqueio
- User populado (se token presente) → claim "schema" disponível ✅
- User vazio (se sem token) → mesmo comportamento de antes
Endpoints com [Authorize]:
- Funcionamento idêntico (lazy auth ou auth normal produzem mesmo resultado)
9. Referências
Documentação Microsoft Learn
| Tópico | URL |
|---|---|
| ASP.NET Core Middleware | https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/ |
| Middleware order (seção) | https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/#middleware-order |
| Authentication overview | https://learn.microsoft.com/en-us/aspnet/core/security/authentication/ |
| Authorization introduction | https://learn.microsoft.com/en-us/aspnet/core/security/authorization/introduction |
Simple authorization ([Authorize], [AllowAnonymous]) | https://learn.microsoft.com/en-us/aspnet/core/security/authorization/simple |
| FallbackPolicy / Require authenticated users | https://learn.microsoft.com/en-us/aspnet/core/security/authorization/secure-data#require-authenticated-users |
| Routing in ASP.NET Core | https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing |
Código Fonte (dotnet/aspnetcore — MIT License)
| Arquivo | URL |
|---|---|
| AuthenticationMiddleware.cs | https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Core/src/AuthenticationMiddleware.cs |
| AuthorizationMiddleware.cs | https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs |
Código Fonte Coezzion
| Arquivo | Descrição |
|---|---|
Startup.cs | Ordem de chamada dos middlewares (4 serviços) |
ApiConfig.cs | UseRouting, UseCors, UseAuthorization, UseEndpoints |
AuthenticationConfig.cs (nuget) | AddAuthentication(JwtBearer), UseAuthentication, UseAuthorization |
AuthenticationExtensions.cs | AddSecondaryAuthentication — PaymentsScheme |
Apêndice: Glossário
| Termo | Definição |
|---|---|
| Terminal middleware | Middleware que não chama _next — interrompe o pipeline |
| Lazy authentication | UseAuthorization dispara AuthenticateAsync sob demanda quando encontra [Authorize], mesmo sem UseAuthentication ter executado |
| FallbackPolicy | Política aplicada a TODOS endpoints (exceto [AllowAnonymous]) quando configurada — não configurada no Coezzion |
| DefaultPolicy | Política usada pelo [Authorize] sem parâmetros — exige usuário autenticado (DenyAnonymousAuthorizationRequirement) |
| IAuthenticationRequestHandler | Handler que pode short-circuitar UseAuthentication (ex: redirect OAuth) |
| IPolicyEvaluator | Serviço que orquestra AuthenticateAsync + AuthorizeAsync dentro do AuthorizationMiddleware |