Summary
Ensure plugin contributed middleware can be injected into the gRPC pipeline, not only the HTTP pipeline.
ASP.NET Core gRPC does not use HttpContext based middleware the same way as HTTP, it relies on interceptors (Grpc.Core.Interceptors.Interceptor). This sub issue defines how PluginMiddleware , gated by its Transport, maps onto the gRPC pipeline, how PipelinePosition is interpreted for gRPC, and how the host handles HTTP only middleware on the gRPC transport.
Goal
Allow plugins to contribute cross cutting behavior (auth, logging, correlation ids, etc.) to the gRPC transport with the same declarative model used for HTTP, while keeping the contract explicit about what is and is not supported.
The implementation must reuse existing gRPC interceptor infrastructure no custom gRPC pipeline engine and no second interceptor framework should be introduced.
Background
A plugin written for HTTP middleware typically depends on HttpContext. In gRPC:
- the request context is
ServerCallContext, not HttpContext,
- interceptors compose as call chain (not as six native insertion points),
- and some HTTP only middleware cannot be adapted.
Without an explicit contract, plugins either break on gRPC or silently do nothing. The host must define deterministic, documented behavior driven by the declared transport, never by reflection guesses.
Scope
C7. Middleware in gRPC pipeline
The host must support injecting plugin middleware into the gRPC pipeline for entries declared with Transport = Grpc:
- compose
PipelinePosition as AuthKit semantic positions into the gRPC interceptor chain (not as six native ASP.NET Core points),
- resolve the interceptor from the request service provider, mirroring ,
- and handle HTTP only middleware explicitly (skip with warning) instead of attempting an automatic
HttpContext bridge.
C7. Middleware in gRPC Pipeline
Registration model (transport gated)
Plugins declare PluginMiddleware. The Transport field selects the target; the host never inspects a middleware to guess its transport:
// HTTP only behavior
new PluginMiddleware(typeof(MyHttpMiddleware), PipelinePosition.BeforeAuthentication,
Transport = AuthKitTransport.Http);
// gRPC native behavior
new PluginMiddleware(typeof(MyGrpcInterceptor), PipelinePosition.BeforeAuthentication,
Transport = AuthKitTransport.Grpc);
For gRPC, MiddlewareType should be concrete Grpc.Core.Interceptors.Interceptor subclass. If unified plugin contract is desired, the host may offer very thin IAuthKitGrpcInterceptor adapter that it wraps into an Interceptor but this must not become second interceptor framework, the canonical accepted type is Interceptor.
PipelinePosition is semantic, not native
PipelinePosition values are AuthKit semantic positions, not native ASP.NET Core gRPC insertion points. The gRPC interceptor chain forms single call pipeline, the host is responsible for mapping each semantic position onto the correct place relative to the host's own interceptors.
| PipelinePosition |
gRPC meaning |
BeforeRouting |
before call handling begins |
AfterRouting |
after endpoint/method resolution |
BeforeAuthentication |
before the host authentication interceptor |
AfterAuthorization |
after the host authorization interceptor |
BeforeEndpoints |
directly before the service method |
AfterEndpointExecution |
post processing after the service method |
These are AuthKit level semantics and must be documented as such they are not one to one with native ASP.NET Core pipeline stages.
Host composition and ordering
The host collects every PluginMiddleware with Transport = Grpc, then:
- group by
PipelinePosition,
- sort by deterministic ordering (
Order, then stable PluginId, then DeclarationIndex),
- compose into the gRPC interceptor chain, interleaving plugin interceptors with the host's own interceptors.
Example composition:
BeforeRouting -> Plugin C
AfterRouting -> Plugin A
BeforeAuthentication -> (host authentication interceptor)
...
AfterAuthorization -> Plugin B
BeforeEndpoints - Plugin D
gRPC service method
AfterEndpointExecution -> Plugin D (post processing)
-> Plugin B (post processing)
This yields predictable, host driven behavior, the plugin only declares position and order.
AfterEndpointExecution (post processing)
AfterEndpointExecution means the interceptor performs post-processing after the downstream call - which is the natural gRPC interceptor shape:
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request, ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
var response = await continuation(request, context); // downstream call
// post processing here
return response;
}
It is not separate "next interceptor" inserted after the method it is the tail of the same interceptor that wrapped the call.
HttpContext dependent middleware (no reflection, no auto-bridge)
The host must not detect HttpContext usage via reflection (eg. scanning method bodies) - that is fragile and unreliable. Instead:
PluginMiddleware with Transport = Http (or default) that targets IAuthKitMiddleware / AuthKitMiddlewareBase / convention middleware is skipped on the gRPC transport with an explicit warning.
PluginMiddleware with Transport = Grpc whose MiddlewareType is an Interceptor (or the thin IAuthKitGrpcInterceptor adapter) is executed on gRPC.
- An optional
IAuthKitGrpcMiddlewareAdapter may be registered explicitly by plugin that wants to share logic between transports; the host never auto creates this bridge.
No automatic HttpContext -> ServerCallContext bridge is provided by default, because partial/fake HttpContext creates unsafe expectations (Request.Path, Response, Connection, full pipeline next, etc.).
Default behavior matrix:
IAuthKitMiddleware (Transport = Http) -> gRPC: SKIP + explicit warning
Interceptor (Transport = Grpc) -> gRPC: EXECUTE
explicit IAuthKitGrpcMiddlewareAdapter -> gRPC: EXECUTE (plugin declared)
Streaming support
gRPC is not limited to unary RPCs. The interceptor must work for all four call patterns:
- Unary -> Unary
- Unary -> Stream (server streaming)
- Stream -> Unary (client streaming)
- Stream -> Stream (duplex streaming)
Because the contract reuses Grpc.Core.Interceptors.Interceptor, the base class already provides the corresponding overloads (UnaryServerHandler, ClientStreamingServerHandler, ServerStreamingServerHandler, DuplexStreamingServerHandler). No custom streaming pipeline is required this must be covered by the acceptance and integration checks.
Validation (C6 extension)
The contract validator is extended to recognize gRPC interceptor registrations:
Transport = Grpc entries must have MiddlewareType that is public, concrete, non abstract, non generic Interceptor subclass,
- abstract
Interceptor subclasses, open/closed generic Interceptor subclasses, and arbitrary non Interceptor types are rejected,
- with the same explicit, plugin naming error messages used for HTTP middleware.
Architecture
IAuthKitPlugin
|
v
PluginMiddleware
|
+---------+---------+
| |
HTTP gRPC
| |
IAuthKitMiddleware Interceptor
| |
ASP.NET middleware gRPC interceptor chain
PipelinePosition (shared semantic abstraction)
|
+---------+---------+
| |
HTTP gRPC
| |
native middleware interceptor chain
positions (host composes)
PipelinePosition remains the common abstraction; the host maps it onto each transport's pipeline. The plugin does not need to know how the host technically builds the pipeline.
Backward Compatibility
This sub issue is additive.
- Plugins that only target HTTP are unaffected.
IAuthKitPlugin is unchanged gRPC support is an additional host capability selected via Transport.
- Existing HTTP middleware continues to work for the HTTP transport on gRPC it is skipped with warning.
DevTokens must continue to compile and load successfully; if it has no gRPC middleware, nothing changes.
Validation
C7. Middleware in gRPC Pipeline
Integration
Acceptance Criteria
- Plugin middleware is supported on the gRPC transport via
Interceptor subclasses, selected by Transport = Grpc.
PipelinePosition is mapped as AuthKit semantic positions into the gRPC interceptor chain deterministically and documented as such.
- gRPC interceptors can use scoped services from the request service provider (single scope).
- HTTP only middleware is explicitly skipped on gRPC with warning it is never silently no op'd or auto bridged.
- The contract validator covers gRPC interceptor registrations (concrete
Interceptor, non generic, non abstract).
- Streaming RPCs (unary, server streaming, client streaming, duplex) are supported via the base
Interceptor overloads.
- Existing HTTP behavior is unchanged.
DevTokens continues to compile and load successfully.
Non Goals
This sub issue does not include:
- a second AuthKit interceptor framework (we reuse
Grpc.Core.Interceptors.Interceptor),
- an automatic
HttpContext -> ServerCallContext bridge,
- reflection based transport detection,
- new authentication/authorization semantics,
- changing
PipelinePosition values,
- or custom gRPC pipeline engine.
Summary
Ensure plugin contributed middleware can be injected into the gRPC pipeline, not only the HTTP pipeline.
ASP.NET Core gRPC does not use
HttpContextbased middleware the same way as HTTP, it relies on interceptors (Grpc.Core.Interceptors.Interceptor). This sub issue defines howPluginMiddleware, gated by itsTransport, maps onto the gRPC pipeline, howPipelinePositionis interpreted for gRPC, and how the host handles HTTP only middleware on the gRPC transport.Goal
Allow plugins to contribute cross cutting behavior (auth, logging, correlation ids, etc.) to the gRPC transport with the same declarative model used for HTTP, while keeping the contract explicit about what is and is not supported.
The implementation must reuse existing gRPC interceptor infrastructure no custom gRPC pipeline engine and no second interceptor framework should be introduced.
Background
A plugin written for HTTP middleware typically depends on
HttpContext. In gRPC:ServerCallContext, notHttpContext,Without an explicit contract, plugins either break on gRPC or silently do nothing. The host must define deterministic, documented behavior driven by the declared transport, never by reflection guesses.
Scope
C7. Middleware in gRPC pipeline
The host must support injecting plugin middleware into the gRPC pipeline for entries declared with
Transport = Grpc:PipelinePositionas AuthKit semantic positions into the gRPC interceptor chain (not as six native ASP.NET Core points),HttpContextbridge.C7. Middleware in gRPC Pipeline
Registration model (transport gated)
Plugins declare
PluginMiddleware. TheTransportfield selects the target; the host never inspects a middleware to guess its transport:For gRPC,
MiddlewareTypeshould be concreteGrpc.Core.Interceptors.Interceptorsubclass. If unified plugin contract is desired, the host may offer very thinIAuthKitGrpcInterceptoradapter that it wraps into anInterceptorbut this must not become second interceptor framework, the canonical accepted type isInterceptor.PipelinePosition is semantic, not native
PipelinePositionvalues are AuthKit semantic positions, not native ASP.NET Core gRPC insertion points. The gRPC interceptor chain forms single call pipeline, the host is responsible for mapping each semantic position onto the correct place relative to the host's own interceptors.BeforeRoutingAfterRoutingBeforeAuthenticationAfterAuthorizationBeforeEndpointsAfterEndpointExecutionThese are AuthKit level semantics and must be documented as such they are not one to one with native ASP.NET Core pipeline stages.
Host composition and ordering
The host collects every
PluginMiddlewarewithTransport = Grpc, then:PipelinePosition,Order, then stablePluginId, thenDeclarationIndex),Example composition:
This yields predictable, host driven behavior, the plugin only declares position and order.
AfterEndpointExecution (post processing)
AfterEndpointExecutionmeans the interceptor performs post-processing after the downstream call - which is the natural gRPC interceptor shape:It is not separate "next interceptor" inserted after the method it is the tail of the same interceptor that wrapped the call.
HttpContext dependent middleware (no reflection, no auto-bridge)
The host must not detect
HttpContextusage via reflection (eg. scanning method bodies) - that is fragile and unreliable. Instead:PluginMiddlewarewithTransport = Http(or default) that targetsIAuthKitMiddleware/AuthKitMiddlewareBase/ convention middleware is skipped on the gRPC transport with an explicit warning.PluginMiddlewarewithTransport = GrpcwhoseMiddlewareTypeis anInterceptor(or the thinIAuthKitGrpcInterceptoradapter) is executed on gRPC.IAuthKitGrpcMiddlewareAdaptermay be registered explicitly by plugin that wants to share logic between transports; the host never auto creates this bridge.No automatic
HttpContext->ServerCallContextbridge is provided by default, because partial/fakeHttpContextcreates unsafe expectations (Request.Path,Response,Connection, full pipelinenext, etc.).Default behavior matrix:
Streaming support
gRPC is not limited to unary RPCs. The interceptor must work for all four call patterns:
Because the contract reuses
Grpc.Core.Interceptors.Interceptor, the base class already provides the corresponding overloads (UnaryServerHandler,ClientStreamingServerHandler,ServerStreamingServerHandler,DuplexStreamingServerHandler). No custom streaming pipeline is required this must be covered by the acceptance and integration checks.Validation (C6 extension)
The contract validator is extended to recognize gRPC interceptor registrations:
Transport = Grpcentries must haveMiddlewareTypethat is public, concrete, non abstract, non genericInterceptorsubclass,Interceptorsubclasses, open/closed genericInterceptorsubclasses, and arbitrary nonInterceptortypes are rejected,Architecture
PipelinePositionremains the common abstraction; the host maps it onto each transport's pipeline. The plugin does not need to know how the host technically builds the pipeline.Backward Compatibility
This sub issue is additive.
IAuthKitPluginis unchanged gRPC support is an additional host capability selected viaTransport.DevTokensmust continue to compile and load successfully; if it has no gRPC middleware, nothing changes.Validation
C7. Middleware in gRPC Pipeline
PluginMiddlewareentries requireTransport = Grpcwith anInterceptorMiddlewareType.PipelinePositionis documented as AuthKit semantic positions composed into the interceptor chain (not six native points).PipelinePosition, sorts deterministically, and composes the interceptor chain.Transport = Http) is skipped on gRPC with an explicit warning.HttpContextdetection and no automaticHttpContext->ServerCallContextbridge.Integration
Interceptor.DevTokenscontinues to load.PluginContractValidatorpasses (including nonInterceptorgRPC type being rejected).Acceptance Criteria
Interceptorsubclasses, selected byTransport = Grpc.PipelinePositionis mapped as AuthKit semantic positions into the gRPC interceptor chain deterministically and documented as such.Interceptor, non generic, non abstract).Interceptoroverloads.DevTokenscontinues to compile and load successfully.Non Goals
This sub issue does not include:
Grpc.Core.Interceptors.Interceptor),HttpContext->ServerCallContextbridge,PipelinePositionvalues,