Skip to content

Interface-factory [Remote] authorization denials are lost over HTTP (client sees 5xx, not NotAuthorizedException) #103

Description

@keithdv

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

  1. The interface renderer emits the throw inside Local{Method}Core, which runs server-side (src/Generator/Renderer/InterfaceFactoryRenderer.cs:456).
  2. 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), …)).
  3. 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).
  4. src/RemoteFactory.AspNetCore/WebApplicationExtensions.cs:83-90 (MapPost /api/neatoo) has no exception filter, so the request surfaces as a 5xx.
  5. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions