dotnet / dotnet/aspnetcore

Define middleware state responsibilities during endpoint rerouting and re-execution

Open
#69,282 1 comment 0 reactions 0 assignees View on GitHub
area-middleware design-proposal
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

## Summary

Define and document the contract for endpoint-aware middleware when a request is rerouted or the pipeline is re-executed and a second endpoint is selected. The proposed baseline is to justify and make explicit the existing model: rerouting does not rewind `HttpContext`, clear arbitrary component state, or automatically replay every middleware that observed the first endpoint. Unless a component documents a stronger guarantee, applications using advanced rerouting/re-execution are responsible for arranging middleware after the final endpoint selection or explicitly resetting state/content before re-execution.

## Motivation and goals

Several defense-in-depth investigations expose the same architectural question from different layers:

- #68581 asks whether automatically inserted authentication and authorization should replay after implicit rerouting.
- #68582 asks whether endpoint execution should reject authorization state produced for another endpoint.
- #68614 covers deferred CORS state produced for one endpoint and applied after re-execution selects another.
- #67559 covers endpoint security-metadata guards and what evidence proves the relevant middleware handled the selected endpoint.
- #68476 covers scoping endpoint/component-associated state so that it cannot be reused outside the context that registered it. These are not necessarily the same defect and should not all receive the same implementation. They do show that the framework lacks one discoverable statement of who owns state across endpoint changes. The goals are to:

- Document that rerouting/re-execution does not generally reset `HttpContext` or replay endpoint-aware middleware.
- Catalog which first-party middleware initiates rerouting and exactly which state each initiator clears, restores, or preserves.
- Catalog endpoint-aware middleware decisions: preserve the first result, recompute, overwrite deferred state, fail closed, or require explicit application ordering.
- Make developer responsibilities actionable: place middleware after final routing, explicitly compose it into a rerouted branch, or clear/reset relevant state and response content before re-execution.
- Identify narrowly scoped component bugs where existing documented behavior requires a stronger guarantee, without turning those fixes into a universal replay contract.

## In scope

- Endpoint-changing URL rewriting, status-code re-execution, exception-handler re-execution, and other first-party rerouting mechanisms.
- State stored in `HttpContext.Features`, `HttpContext.Items`, `HttpContext.Endpoint`, route values, request path/path base, response headers/body, and deferred `OnStarting` callbacks.
- Authentication, authorization, CORS, antiforgery, rate limiting, output/response caching, endpoint metadata guards, and other middleware found by the audit.
- A per-component decision table documenting:
- whether the component initiates or is affected by re-execution;
- whether it clears or replaces its own state;
- whether the rerouting middleware clears/restores that state;
- whether applications can reset/re-run it through public APIs;
- which endpoint's metadata/result controls the final response.
- Guidance for advanced applications that change endpoints after endpoint-aware middleware has run.
- Child issues for concrete component behavior, tests, or documentation changes.

## Out of scope

- A universal mechanism that snapshots and restores every possible `HttpContext` mutation.
- Automatically replaying arbitrary application or third-party middleware, which may have side effects and may not be idempotent.
- Treating all related child issues as duplicates or requiring them to use one implementation strategy.
- Changing the documented `HttpResponse.OnStarting` callback ordering contract.
- Guaranteeing that custom rerouting middleware participates without explicitly composing or resetting the relevant pipeline state.
- Making endpoint metadata retroactively affect middleware that ran before the final endpoint was selected.

## Risks / unknowns

- Developers may interpret “rerouting” as restarting the request, even though the same `HttpContext`, response, features, items, and deferred callbacks are generally retained.
- Automatically replaying authentication, authorization, claims transformation, request-handler schemes, logging, or custom policy handlers can duplicate observable side effects.
- Clearing state centrally is unsafe when the framework does not own that state and cannot know whether preserving it is required.
- Preserving state can be unsafe when it represents a decision tied to the first endpoint, such as a deferred CORS grant.
- Component-specific behavior may differ legitimately. For example, a no-policy error endpoint may intentionally inherit CORS headers, while an explicit `[DisableCors]` endpoint must replace the earlier decision.
- Fail-closed guards can improve defense in depth but can also break advanced pipelines that intentionally manage ordering themselves.
- Public reset/replay hooks may be missing for some components. The catalog should identify those gaps before proposing new API.

## Examples

Applications that rewrite to protected endpoints should make the final ordering explicit rather than relying on automatically inserted middleware:

```csharp
app.UseRouting();
app.UseRewriter();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
```

A re-executing middleware that changes the endpoint or response content is responsible for clearing the state it owns and restoring request state when it returns. It cannot assume the framework will clear unrelated component state:

```csharp
var originalPath = context.Request.Path;
var originalEndpoint = context.GetEndpoint();
try
{
context.SetEndpoint(null);
context.Request.RouteValues.Clear();
context.Request.Path = errorPath;
context.Response.Clear();
await reexecutePipeline(context);
}
finally
{
context.Request.Path = originalPath;
context.SetEndpoint(originalEndpoint);
}
```

Component middleware that defers an endpoint-specific decision should own replacement or invalidation of that decision if it explicitly supports re-execution. Otherwise, applications must position that middleware so it runs against the finally selected endpoint.

Contributor guide

Open the contributing guide

Research direction

Start by tracing the first-party rerouting entry points named here: UseRewriter, status-code re-execution, and exception-handler re-execution. Compare them with the listed endpoint-aware middleware and the HttpContext state they clear, restore, or preserve. Done means a reviewed per-component decision table and actionable guidance, with narrowly scoped child issues identified.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
backend-api-design, documentation
Issue type
Documentation
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.