Apricot Framework

Error definitions

Why an onward token failure is not a 401, the three codes a client needs text for, and giving the empty 401 and 403 a body.

builder.Services.AddErrorDefinitions();
builder.Services.AddJwtBearerAuthentication(builder.Configuration);
builder.Services.AddAuthorization();
builder.Services.AddAuthenticationErrorDefinitions();

var app = builder.Build();

app.UseExceptionHandler();

A mapper with no handler does nothing. The host still calls AddErrorDefinitions and UseExceptionHandler; where the handler sits in the pipeline is its decision, not this library's.

An onward failure is not the caller's fault

This is the reason the package exists rather than a one-line mapping. When a service cannot obtain a token for a call of its own, the caller did nothing wrong and can do nothing about it. Answering 401 would tell them to re-authenticate, which cannot help, and would record a service fault as a client error in every dashboard that counts by status class.

FailureKindCodeStatus
The request carried no identified callernot_authenticatedAUTH_NO_PRINCIPAL401
A token could not be obtained, and waiting may helpunavailableAUTH_CLIENT_TOKEN_UNAVAILABLE503
A token could not be obtained, and waiting will notinternalAUTH_CLIENT_MISCONFIGURED500
Authorization refused an identified calleraccess_deniedACCESS_DENIED403

Only ClientAuthenticationFailure.Unavailable is the retryable case; every other reason is a deployment that needs changing, and is reported as an internal fault of this service.

Three codes, because that is how many outcomes a client acts on differently. Which provider, which credential and which reason travel in the reason payload, so the set a client needs text for does not grow every time a new way to fail is recognised. Nothing else from the failure reaches the response: the messages name the authority and the client, and a provider's own error description can quote the request that carried the secret.

The empty 401 and 403

Neither is an exception, so neither reaches an exception handler. Without this package a service answers in one contract for every failure except the two a client meets most often — which is where a consistent error contract is worth the most.

AddAuthenticationErrorDefinitions gives both a body. It decorates the framework's authorization result handler rather than replacing it, and writes only after it has run, so the status and the WWW-Authenticate header RFC 6750 requires on a 401 are the framework's own. An answer that already has a body — a policy that reported for itself — is left alone.

HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json
WWW-Authenticate: Bearer

{"type":"about:blank","title":"Not authenticated","status":401,
 "detail":"The request is not authenticated.","instance":"/api/orders",
 "errors":[{"kind":"not_authenticated","code":"AUTH_NO_PRINCIPAL",
            "message":"The request is not authenticated."}]}

Note

This covers answers that came from the authorization middleware, which is every endpoint carrying a policy — anything with [Authorize] or [BearerAuthorize]. A challenge issued from an endpoint by hand is left alone.

Cancellation is deliberately not mapped here. ApricotFramework.ErrorDefinitions already classifies it, and a cancelled token request throws OperationCanceledException rather than a ClientAuthenticationException so that it reaches that mapper unchanged.

On this page