Step 5: Govern Endpoints
Status: Complete with AsiBackbone 7.2.1. First released in v0.1.0-alpha.5 on AsiBackbone 7.2.0; see Capability before acknowledgment for why the step now requires 7.2.1.
Outcome
The versioned GovernedOperationsController demonstrates governance on real application endpoints before their action
bodies execute. It keeps a read-only description route intentionally ungoverned and places three unsafe operations
behind explicit host-owned rules:
| Endpoint | Governance metadata | Successful outcome |
|---|---|---|
GET /api/v1/governed-operations |
None; explicitly anonymous | Returns the read-only sample description. |
POST /api/v1/governed-operations/administrator |
AdministratorActionPolicy |
Executes for an authenticated actor in a configured administrator role. |
POST /api/v1/governed-operations/capability |
CapabilityProtectedActionPolicy and referenceapplication.consequential.execute capability |
Executes when the actor holds the declared capability. |
POST /api/v1/governed-operations/consequential |
All three policy markers, the capability scope, and acknowledgment metadata | Challenges an entitled administrator, then executes only on a same-actor, same-operation, unexpired, one-time acknowledged retry. |
The POST routes keep NCAT's authenticated-by-default authorization policy and validate antiforgery tokens explicitly
with [ValidateAntiForgeryToken], matching NCAT's own AccountController. Endpoint governance adds an execution
boundary; it does not replace either control.
For local exploration without an external identity provider, the supplemental
Development-only demo identity controls can issue a temporary cookie containing fixed,
selectable walkthrough claims. The control is off by default and is not registered outside Development.
Attach endpoint metadata
Controller attributes make the protected operations and the order-independent requirements visible next to the action:
[HttpPost("consequential")]
[ValidateAntiForgeryToken]
[GovernancePolicy(typeof(AdministratorActionPolicy))]
[GovernancePolicy(typeof(CapabilityProtectedActionPolicy))]
[GovernancePolicy(typeof(ConsequentialActionPolicy))]
[RequireCapabilityGrant(ReferenceApplicationGovernancePolicySet.ConsequentialExecutionScope)]
[RequireAcknowledgment]
public ActionResult<GovernedOperationResponse> ExecuteConsequentialAction()
Routing selects that endpoint, NCAT authenticates and authorizes the request, and AsiBackbone evaluates its metadata before MVC invokes the action. A denied decision or an acknowledgment challenge therefore prevents the response text inside the action from being produced.
GET /api/v1/governed-operations is a read-only description. It is marked AllowAnonymous and has no AsiBackbone
metadata, so the middleware passes it through. This is deliberate rather than an omission: reading the sample does not
perform the operation being governed.
Validate capability grants
ClaimEndpointCapabilityGrantValidator is the host-owned grant boundary. AsiBackbone supplies the endpoint's declared
scopes but does not infer where grants come from. The validator reads the current actor's authenticated identities and
requires every declared scope to appear as an exact claim using
ApplicationAuthorizationOptions.PermissionClaimType.
With the shipped NCAT configuration, authentication-provider claims such as permission, permissions, scope, or
scp are normalized to application:permission. Providers should issue one claim value per capability. A scope in
endpoint metadata only states what the operation requires; it never proves the actor holds that grant.
The validator preserves only a decision that is already denied. Every other decision, including one that requires
acknowledgment, must pass the grant check, and a missing grant is denied with
referenceapplication.capability.grant_required. AsiBackbone 7.2.1 calls the validator for every decision that is not
already denied, so a missing grant always takes precedence over an acknowledgment challenge. Registration is scoped to
the request:
services.AddScoped<IEndpointCapabilityGrantValidator, ClaimEndpointCapabilityGrantValidator>();
A claim proves entitlement but does not tie a grant to any particular request. The supplemental Bind capabilities to signed tokens procedure adopts AsiBackbone's second model, signed capability tokens bound to one actor, operation, and payload, and compares the two.
Map HTTP outcomes
The endpoint middleware now uses the application's Problem Details service for ordinary governance denials. The body is intentionally generic: it reports HTTP 403 without exposing policy internals, while NCAT adds its normal request and trace correlation fields.
An otherwise-allowed consequential request produces AsiBackbone's acknowledgment challenge with HTTP 428
Precondition Required. The challenge includes the handshake identifier, required acknowledgment code, and stable
acknowledgment reason code. The host persists that request in AsiBackbone's acknowledgment tables with a five-minute
validity window.
The actor submits the handshake identifier and required code to POST /api/v1/acknowledgments. The host delegates the
actor and code checks back to IAcknowledgmentChallengeService, persists the resulting acknowledgment, and returns its
identifier plus the X-ReferenceApplication-Acknowledgment retry-header name. A retry presents that identifier:
POST /api/v1/governed-operations/consequential
X-ReferenceApplication-Acknowledgment: <acknowledgment-id>
Before governance runs, the host verifies that the acknowledgment is accepted, unexpired, unused, belongs to the same
actor, and names the selected endpoint's operation. The consequential decision policy then permits the already-
acknowledged evaluation to continue through the ordinary capability validator. The endpoint consumes the
acknowledgment immediately before executing. A second retry is denied as
referenceapplication.acknowledgment.replayed.
The accepted retry's decision ledger record carries both HandshakeId and AcknowledgmentId. They are part of
AsiBackbone's canonical ledger payload, so the existing record hash covers the link. The governance explorer displays
both identifiers on the decision timeline.
The host-owned stable failures include referenceapplication.acknowledgment.expired,
referenceapplication.acknowledgment.replayed, and referenceapplication.acknowledgment.operation_mismatch.
AsiBackbone's challenge service retains its actor/code failures, including
acknowledgment.challenge.actor_mismatch and acknowledgment.challenge.code_mismatch.
The effective order is:
- authentication and ASP.NET Core authorization;
- AsiBackbone policy evaluation;
- host-owned capability validation, for every decision that is not already denied;
- denial or acknowledgment challenge when required;
- MVC antiforgery validation when governance allows the request to continue; and
- MVC action execution only when every boundary permits it.
Antiforgery validation still applies to every unsafe MVC route. The tests obtain a token for the same identity that submits the POST, and separately prove that a request without one is rejected.
Capability before acknowledgment
Step 5 requires AsiBackbone 7.2.1 or later. In 7.2.0, capability validation ran only when the policy decision could
proceed immediately. An acknowledgment-required decision cannot, so on the consequential endpoint the framework skipped
step 3 and issued the acknowledgment challenge directly: an administrator who did not hold
referenceapplication.consequential.execute received HTTP 428 instead of HTTP 403. The action body never executed, but
the actor was offered an acknowledgment for an operation they were not entitled to.
AsiBackbone 7.2.1 fixed this (AsiBackbone/AsiBackbone#887).
Capability validation now runs for every decision that is not already denied, so a missing grant is denied before any
acknowledgment challenge. The validator needed no change for the upgrade, because it already denied a missing grant on
an acknowledgment-required decision. The upgrade is tracked in
AsiBackbone/ReferenceApplication#17.
Verification
GovernedOperationsEndpointTests exercises the production routes through the complete NCAT test host:
- the anonymous read-only description passes without governance metadata;
- an administrator is allowed and a non-administrator receives safe Problem Details;
- an actor with the exact normalized capability claim is allowed and an actor without it is denied;
- an administrator with the capability receives an HTTP 428 challenge, accepts it, retries, and executes;
- the accepted retry records the handshake and acknowledgment identifiers under the decision record hash;
- a foreign actor, wrong acknowledgment code, expired challenge, and replayed acknowledgment are denied with stable reason codes;
- an actor with the capability but without an administrator role is denied on the consequential endpoint;
- an administrator without the capability is denied on the consequential endpoint with safe Problem Details rather than offered an acknowledgment challenge, as described in Capability before acknowledgment;
- an anonymous caller is rejected by ASP.NET Core authorization before governance runs; and
- an otherwise-valid administrator request without an antiforgery token is rejected.
ClaimEndpointCapabilityGrantValidator is also tested on its own. It requires every declared scope, ignores grants on
unauthenticated identities, preserves an existing denial, and denies a missing grant even when the decision already
requires acknowledgment.
The complete inherited NCAT suite continues to run with the new tests:
dotnet restore ReferenceApplication.slnx --locked-mode
dotnet build ReferenceApplication.slnx --configuration Release --no-restore
dotnet test ReferenceApplication.slnx --configuration Release --no-build --no-restore
Authoritative references
- AsiBackbone ASP.NET Core Endpoint Governance
- AsiBackbone Host-Owned Execution Enforcement
- NCAT Error Handling
- NCAT Authorization