Summary
Extend the AuthKit plugin contract with a structured middleware contract for AuthKit hosts, built on ASP.NET Core middleware primitives (HttpContext / RequestDelegate).
This sub-issue introduces the core contract types:
- a
PipelinePosition enum that declares where middleware should be inserted,
- a
PluginMiddleware registration record returned by the plugin,
- an
IsMiddlewareEnabled toggle per middleware,
- an
AuthKitMiddlewareBase abstract class for simple/convention-based implementations,
- and an
IAuthKitMiddleware interface for DI-aware (scoped) middleware.
The plugin declares what middleware it needs; the host decides how to activate it and connect it to the pipeline. Middleware activation and dependency resolution are host responsibilities.
Note: because the contract is expressed in terms of HttpContext and RequestDelegate, it is HTTP/ASP.NET Core-specific, not strictly host gnostic. A truly host agnostic abstraction would require separate adapter - C7 addresses gRPC through an interceptor adapter. C1–C5 should be read as an AuthKit host contract built on ASP.NET Core primitives.
Goal
Provide first class, strongly typed plugin APIs for contributing middleware, so that:
- plugin middleware is ordered deterministically and predictably,
- plugins declare middleware by type the host owns activation and resolution,
- disabled middleware is skipped without side effects,
- and the host pipeline remains consistent and debuggable.
No custom middleware engine should be introduced the host reuses the existing ASP.NET Core pipeline.
Background
Today plugins have no unified way to register middleware. Each plugin works around the host, leading to:
- unpredictable ordering,
- duplicated or conflicting registration,
- inconsistent enable/disable semantics,
- and middleware that cannot use scoped services.
A structured contract lets the host own ordering and diagnostics while plugins stay declarative.
Scope
C1. PipelinePosition enum
Define the insertion points available to plugins:
public enum PipelinePosition
{
BeforeRouting,
AfterRouting,
BeforeAuthentication,
AfterAuthorization,
BeforeEndpoints,
AfterEndpointExecution
}
C2. Multiple middleware
Let plugin return collection instead of single middleware:
IReadOnlyList<PluginMiddleware> Middlewares { get; }
where PluginMiddleware declares the middleware type and its position.
C3. IsMiddlewareEnabled
Each PluginMiddleware has an IsMiddlewareEnabled flag. Disabled entries are skipped by the host.
C4. AuthKitMiddlewareBase
A convenience/convention-based abstract class simplifying implementation:
public abstract class AuthKitMiddlewareBase
{
public abstract Task InvokeAsync(HttpContext context, RequestDelegate next);
}
C5. IAuthKitMiddleware (DI-aware)
A DI aware interface for middleware that needs scoped services:
public interface IAuthKitMiddleware
{
Task InvokeAsync(HttpContext context, RequestDelegate next);
}
The host resolves an implementation from the request service provider, so constructor-injected scoped services participate in the same request scope.
C1. PipelinePosition
Proposed Contract
public enum PipelinePosition
{
BeforeRouting,
AfterRouting,
BeforeAuthentication,
AfterAuthorization,
BeforeEndpoints,
AfterEndpointExecution
}
Host Pipeline Mapping
Each value maps to well defined point in the standard ASP.NET Core pipeline:
BeforeRouting -> before UseRouting
AfterRouting -> after UseRouting, before authentication
BeforeAuthentication -> before UseAuthentication
AfterAuthorization -> after UseAuthorization (ie. after authentication AND authorization)
BeforeEndpoints -> before UseEndpoints
AfterEndpointExecution -> post-endpoint execution (response post-processing)
AfterEndpointExecution explicit semantics
AfterEndpointExecution represents post endpoint execution: the middleware runs on the return/response path after the endpoint has executed. It must not be interpreted merely as "registered after UseEndpoints()". The host must treat it as response post processing, not as a second independent pipeline tail.
AfterAuthorization explicit semantics
ASP.NET Core has two distinct steps, UseAuthentication() and UseAuthorization(). AfterAuthorization means after both after authentication and authorization. There is intentionally no separate AfterAuthentication position plugin requiring "after authentication but before authorization" cannot be expressed and must be documented as unsupported.
Deterministic Ordering
Within single PipelinePosition, the host orders middleware by:
Order
→ PluginId (stable plugin identifier)
→ DeclarationIndex
PluginId must be stable identifier defined by the plugin contract and must not depend on assembly load order or runtime object identity. Ordering must not depend on filesystem ordering, dictionary enumeration, or undefined plugin discovery order.
C2. PluginMiddleware Model
Proposed Contract
public sealed record PluginMiddleware(
Type MiddlewareType,
PipelinePosition Position,
int Order = 0,
bool IsMiddlewareEnabled = true,
string? Name = null);
The plugin declares the middleware type and its position. There is no Factory and no PluginMiddlewareInstance in the contract: middleware activation and dependency resolution are host responsibilities.
Plugin declares WHAT, host decides HOW
PLUGIN
declares: MiddlewareType + Position + Order + Enabled + Name
HOST
validates
filters disabled
deterministic sort (Order, PluginId, DeclarationIndex)
resolves from request IServiceProvider
adapts to ASP.NET Core pipeline
The plugin must not control the host pipeline construction mechanism. It only supplies the type; the host decides how to construct and connect it (eg. UseMiddleware<T>() or resolving IAuthKitMiddleware from the request services).
Host Aggregation
The host collects PluginMiddleware from every plugin and builds one ordered pipeline. A middleware that throws at construction or registration time must surface an explicit error identifying the owning plugin.
C3. IsMiddlewareEnabled
Behavior
When IsMiddlewareEnabled is false, the host must not insert the middleware and must not allocate its instance. This is distinct from a middleware that is present but no op.
Disabled middleware must not affect the ordering of other middleware.
C4. AuthKitMiddlewareBase
Design
public abstract class AuthKitMiddlewareBase
{
public abstract Task InvokeAsync(HttpContext context, RequestDelegate next);
}
This is the convention-based / convenience option: plugins implement InvokeAsync directly without needing DI. It does not imply "no DI" the host may still make scoped services available through its standard activation mechanism; the base class simply removes the need for the plugin to implement an interface.
C5. IAuthKitMiddleware (DI aware)
Proposed Contract
public interface IAuthKitMiddleware
{
Task InvokeAsync(HttpContext context, RequestDelegate next);
}
Host Resolution single request scope
The host resolves IAuthKitMiddleware from the request service provider (the IServiceProvider already flowing through the request), so the middleware's scoped dependencies (DbContext, repositories, etc.) participate in the same request scope as the rest of the request:
HTTP Request
│
▼
Request IServiceProvider
├── AuthKit middleware
├── Plugin services
├── DbContext
└── repositories
The host must not create separate "plugin scope" per middleware. Doing so would place middleware's DbContext in a different scope than the rest of the request, causing lifetime/leak issues. Middleware is activated within the existing request scope.
Signature Note
The delegate parameter must be named next (the downstream RequestDelegate) to match ASP.NET Core conventions and the contract validator (C6).
Host Pipeline Integration
C1–C5 define the contract. The host is responsible for:
- reading
Middlewares from each plugin,
- validating each entry (see C6),
- filtering disabled entries,
- ordering deterministically (Order → stable PluginId → DeclarationIndex),
- inserting at the correct
PipelinePosition,
- resolving
IAuthKitMiddleware from the request service provider (single scope),
- and skipping disabled entries.
Plugin middleware must compose with host middleware and with other plugins' middleware without overriding unrelated pipeline configuration.
Key Principle
PluginMiddleware declares the middleware type. Middleware activation and dependency resolution are host responsibilities. Plugins must not control the host pipeline construction mechanism.
Backward Compatibility
This sub issue is additive.
- Existing plugins without
Middlewares continue to compile and load.
IAuthKitPlugin keeps its existing members; Middlewares is new optional member (default empty).
- Default interface implementations or host fallbacks may be used so existing plugins need no changes.
DevTokens must continue to compile and load successfully.
Validation
C1. PipelinePosition
C2. Multiple middleware
C3. IsMiddlewareEnabled
C4. AuthKitMiddlewareBase
C5. IAuthKitMiddleware
Integration
Acceptance Criteria
- Plugins can declare middleware via
PipelinePosition and PluginMiddleware (type based, no Factory).
PipelinePosition unambiguously defines insertion points, including explicit AfterEndpointExecution and AfterAuthorization semantics.
- Multiple middleware per plugin are supported and ordered deterministically (Order -> stable PluginId -> DeclarationIndex).
- Disabled middleware is skipped without side effects.
- Plugins implement middleware via
AuthKitMiddlewareBase (convention) or IAuthKitMiddleware (DI aware).
- DI aware middleware uses scoped services from the request service provider (single scope).
- The plugin declares what the host owns activation and connection.
- Host insertion follows the declared positions.
- Existing plugins remain backward compatible.
PluginContractValidator passes.
DevTokens continues to compile and load successfully.
Non Goals
This sub issue does not include:
- a
Factory or PluginMiddlewareInstance mechanism (the host owns construction),
- a separate "plugin scope" per middleware,
- the contract validator implementation (see C6),
- gRPC pipeline mapping (see C7),
- endpoint mapping or routing policy,
- authentication/authorization hooks,
- or custom middleware engine.
Summary
Extend the AuthKit plugin contract with a structured middleware contract for AuthKit hosts, built on ASP.NET Core middleware primitives (
HttpContext/RequestDelegate).This sub-issue introduces the core contract types:
PipelinePositionenum that declares where middleware should be inserted,PluginMiddlewareregistration record returned by the plugin,IsMiddlewareEnabledtoggle per middleware,AuthKitMiddlewareBaseabstract class for simple/convention-based implementations,IAuthKitMiddlewareinterface for DI-aware (scoped) middleware.The plugin declares what middleware it needs; the host decides how to activate it and connect it to the pipeline. Middleware activation and dependency resolution are host responsibilities.
Goal
Provide first class, strongly typed plugin APIs for contributing middleware, so that:
No custom middleware engine should be introduced the host reuses the existing ASP.NET Core pipeline.
Background
Today plugins have no unified way to register middleware. Each plugin works around the host, leading to:
A structured contract lets the host own ordering and diagnostics while plugins stay declarative.
Scope
C1.
PipelinePositionenumDefine the insertion points available to plugins:
C2. Multiple middleware
Let plugin return collection instead of single middleware:
where
PluginMiddlewaredeclares the middleware type and its position.C3.
IsMiddlewareEnabledEach
PluginMiddlewarehas anIsMiddlewareEnabledflag. Disabled entries are skipped by the host.C4.
AuthKitMiddlewareBaseA convenience/convention-based abstract class simplifying implementation:
C5.
IAuthKitMiddleware(DI-aware)A DI aware interface for middleware that needs scoped services:
The host resolves an implementation from the request service provider, so constructor-injected scoped services participate in the same request scope.
C1. PipelinePosition
Proposed Contract
Host Pipeline Mapping
Each value maps to well defined point in the standard ASP.NET Core pipeline:
AfterEndpointExecution explicit semantics
AfterEndpointExecutionrepresents post endpoint execution: the middleware runs on the return/response path after the endpoint has executed. It must not be interpreted merely as "registered afterUseEndpoints()". The host must treat it as response post processing, not as a second independent pipeline tail.AfterAuthorization explicit semantics
ASP.NET Core has two distinct steps,
UseAuthentication()andUseAuthorization().AfterAuthorizationmeans after both after authentication and authorization. There is intentionally no separateAfterAuthenticationposition plugin requiring "after authentication but before authorization" cannot be expressed and must be documented as unsupported.Deterministic Ordering
Within single
PipelinePosition, the host orders middleware by:PluginIdmust be stable identifier defined by the plugin contract and must not depend on assembly load order or runtime object identity. Ordering must not depend on filesystem ordering, dictionary enumeration, or undefined plugin discovery order.C2. PluginMiddleware Model
Proposed Contract
The plugin declares the middleware type and its position. There is no
Factoryand noPluginMiddlewareInstancein the contract: middleware activation and dependency resolution are host responsibilities.Plugin declares WHAT, host decides HOW
The plugin must not control the host pipeline construction mechanism. It only supplies the type; the host decides how to construct and connect it (eg.
UseMiddleware<T>()or resolvingIAuthKitMiddlewarefrom the request services).Host Aggregation
The host collects
PluginMiddlewarefrom every plugin and builds one ordered pipeline. A middleware that throws at construction or registration time must surface an explicit error identifying the owning plugin.C3. IsMiddlewareEnabled
Behavior
When
IsMiddlewareEnabledisfalse, the host must not insert the middleware and must not allocate its instance. This is distinct from a middleware that is present but no op.Disabled middleware must not affect the ordering of other middleware.
C4. AuthKitMiddlewareBase
Design
This is the convention-based / convenience option: plugins implement
InvokeAsyncdirectly without needing DI. It does not imply "no DI" the host may still make scoped services available through its standard activation mechanism; the base class simply removes the need for the plugin to implement an interface.C5. IAuthKitMiddleware (DI aware)
Proposed Contract
Host Resolution single request scope
The host resolves
IAuthKitMiddlewarefrom the request service provider (theIServiceProvideralready flowing through the request), so the middleware's scoped dependencies (DbContext, repositories, etc.) participate in the same request scope as the rest of the request:The host must not create separate "plugin scope" per middleware. Doing so would place middleware's
DbContextin a different scope than the rest of the request, causing lifetime/leak issues. Middleware is activated within the existing request scope.Signature Note
The delegate parameter must be named
next(the downstreamRequestDelegate) to match ASP.NET Core conventions and the contract validator (C6).Host Pipeline Integration
C1–C5 define the contract. The host is responsible for:
Middlewaresfrom each plugin,PipelinePosition,IAuthKitMiddlewarefrom the request service provider (single scope),Plugin middleware must compose with host middleware and with other plugins' middleware without overriding unrelated pipeline configuration.
Key Principle
Backward Compatibility
This sub issue is additive.
Middlewarescontinue to compile and load.IAuthKitPluginkeeps its existing members;Middlewaresis new optional member (default empty).DevTokensmust continue to compile and load successfully.Validation
C1. PipelinePosition
PipelinePositionenum exists with the six values.AfterEndpointExecutionis explicitly post-endpoint (response) execution, not merely registration afterUseEndpoints().AfterAuthorizationis explicitly after authentication and authorization.C2. Multiple middleware
IReadOnlyList<PluginMiddleware> Middlewaresexists.PluginMiddlewarecarriesMiddlewareType,Position,Order,IsEnabled,Name.Factory/PluginMiddlewareInstanceexists in the contract.C3. IsMiddlewareEnabled
C4. AuthKitMiddlewareBase
InvokeAsync(HttpContext, RequestDelegate)exists.C5. IAuthKitMiddleware
InvokeAsync(HttpContext, RequestDelegate)exists.Integration
DevTokenscontinues to load.PluginContractValidatorpasses.Acceptance Criteria
PipelinePositionandPluginMiddleware(type based, no Factory).PipelinePositionunambiguously defines insertion points, including explicitAfterEndpointExecutionandAfterAuthorizationsemantics.AuthKitMiddlewareBase(convention) orIAuthKitMiddleware(DI aware).PluginContractValidatorpasses.DevTokenscontinues to compile and load successfully.Non Goals
This sub issue does not include:
FactoryorPluginMiddlewareInstancemechanism (the host owns construction),