Summary
An interface-factory [Remote] call that is refused by authorization throws NotAuthorizedException on the server, and that exception is never mapped into the response. Over a real HTTP hop the client sees a 5xx and throws HttpRequestException, indistinguishable from a server crash — the exception type, the message, and Context are all lost.
Class factories do not have this problem: their denial is returned as a serialized Authorized<T> and the client's generated wrapper reconstructs the exception locally, so the message survives.
Mechanism
- The interface renderer emits the throw inside
Local{Method}Core, which runs server-side (src/Generator/Renderer/InterfaceFactoryRenderer.cs:456).
- The remote delegate is typed on the raw return type — no
Authorized wrapper — so there is no channel for a denial to travel as data (e.g. …IAuthorizedServiceFactory.g.cs:79-82, ForDelegate<string>(typeof(GetDataDelegate), …)).
src/RemoteFactory/HandleRemoteDelegateRequest.cs:247-252 logs and rethrows. There is no catch (NotAuthorizedException) and no mapping to a response DTO — contrast AspForbidException at :240-246, which is caught and returned as new RemoteResponseDto(string.Empty).
src/RemoteFactory.AspNetCore/WebApplicationExtensions.cs:83-90 (MapPost /api/neatoo) has no exception filter, so the request surfaces as a 5xx.
src/RemoteFactory/Internal/MakeRemoteDelegateRequestHttpCall.cs:33-38 throws HttpRequestException on the non-success status.
Why the tests do not catch it
The "client/server" integration harness is in-process, not HTTP: src/Tests/RemoteFactory.IntegrationTests/TestContainers/ClientServerContainers.cs:29 uses MakeSerializedServerStandinDelegateRequest, which calls the server's HandleRemoteDelegateRequest directly (:67, :91). A CLR exception thrown server-side therefore propagates unchanged into the test, and the tests pass. Design.Tests uses the same style of stand-in.
No test project drives the real /api/neatoo endpoint for authorization — src/Tests/RemoteFactory.AspNetCore.TestServer exists but is not referenced by any test .csproj.
Impact
A consumer whose client calls a [Factory] interface across HTTP cannot distinguish "you are not allowed to do that" from "the server fell over". Retry logic, error surfacing and telemetry all treat it as an infrastructure fault. This became more visible with v1.10.0, which gives NotAuthorizedException a meaningful message and a Context property — improvements that this path discards.
Suggested fix
Mirror the AspForbidException handling: catch NotAuthorizedException in HandleRemoteDelegateRequest, carry the denial (message and context) in the response DTO, and have the client reconstruct the exception — the same shape the class-factory path already uses via Authorized<T>.
That is a wire-format change and needs a matching client half plus, ideally, the first HTTP-level authorization test in the repo. It is not a small change, which is why it was filed rather than folded into the v1.10.0 message fix.
Context
Found while mapping the wire path for the v1.10.0 NotAuthorizedException fix. Recorded in that release's "What this release does not claim" section.
Summary
An interface-factory
[Remote]call that is refused by authorization throwsNotAuthorizedExceptionon the server, and that exception is never mapped into the response. Over a real HTTP hop the client sees a 5xx and throwsHttpRequestException, indistinguishable from a server crash — the exception type, the message, andContextare all lost.Class factories do not have this problem: their denial is returned as a serialized
Authorized<T>and the client's generated wrapper reconstructs the exception locally, so the message survives.Mechanism
Local{Method}Core, which runs server-side (src/Generator/Renderer/InterfaceFactoryRenderer.cs:456).Authorizedwrapper — so there is no channel for a denial to travel as data (e.g.…IAuthorizedServiceFactory.g.cs:79-82,ForDelegate<string>(typeof(GetDataDelegate), …)).src/RemoteFactory/HandleRemoteDelegateRequest.cs:247-252logs and rethrows. There is nocatch (NotAuthorizedException)and no mapping to a response DTO — contrastAspForbidExceptionat:240-246, which is caught and returned asnew RemoteResponseDto(string.Empty).src/RemoteFactory.AspNetCore/WebApplicationExtensions.cs:83-90(MapPost /api/neatoo) has no exception filter, so the request surfaces as a 5xx.src/RemoteFactory/Internal/MakeRemoteDelegateRequestHttpCall.cs:33-38throwsHttpRequestExceptionon the non-success status.Why the tests do not catch it
The "client/server" integration harness is in-process, not HTTP:
src/Tests/RemoteFactory.IntegrationTests/TestContainers/ClientServerContainers.cs:29usesMakeSerializedServerStandinDelegateRequest, which calls the server'sHandleRemoteDelegateRequestdirectly (:67,:91). A CLR exception thrown server-side therefore propagates unchanged into the test, and the tests pass.Design.Testsuses the same style of stand-in.No test project drives the real
/api/neatooendpoint for authorization —src/Tests/RemoteFactory.AspNetCore.TestServerexists but is not referenced by any test.csproj.Impact
A consumer whose client calls a
[Factory]interface across HTTP cannot distinguish "you are not allowed to do that" from "the server fell over". Retry logic, error surfacing and telemetry all treat it as an infrastructure fault. This became more visible with v1.10.0, which givesNotAuthorizedExceptiona meaningful message and aContextproperty — improvements that this path discards.Suggested fix
Mirror the
AspForbidExceptionhandling: catchNotAuthorizedExceptioninHandleRemoteDelegateRequest, carry the denial (message and context) in the response DTO, and have the client reconstruct the exception — the same shape the class-factory path already uses viaAuthorized<T>.That is a wire-format change and needs a matching client half plus, ideally, the first HTTP-level authorization test in the repo. It is not a small change, which is why it was filed rather than folded into the v1.10.0 message fix.
Context
Found while mapping the wire path for the v1.10.0
NotAuthorizedExceptionfix. Recorded in that release's "What this release does not claim" section.