Identity and permission
Authentication turns accepted credentials into an identity the application can use. Authorization decides whether that identity satisfies an operation’s conditions. A validated access token can identify a person without granting access to every document.
Start diagnosis by separating credential validation failure from missing permission. Under an explicit JWT bearer scheme, a challenge indicates 401; a forbid for an authenticated identity lacking access indicates 403.
Trust in the token
JWT text can be decoded without establishing trust. The API must validate signature, issuer, audience and lifetime under its established configuration. Authority and Audience must correspond to the intended issuer and resource, with metadata and keys obtained through the configured trust mechanism.
Do not substitute a readable name or a body owner for that validation. After validation, also establish which claim names the policy actually reads.
Select the effective policy
RequireAuthorization without a specific policy uses DefaultPolicy. A named policy selects its own requirements. A configured FallbackPolicy covers endpoints without authorization metadata; it is not a condition list automatically added to every named policy.
If Read must require authentication, permission=read and Admin, compose those conditions explicitly. Also inspect AllowAnonymous: middleware can authenticate but does not enforce authorization failure on that endpoint.
Requirements and alternatives
Within a policy, all requirements must be satisfied. Two handlers can represent alternative paths for the same requirement, such as team membership or valid delegation. When one path does not apply, leaving the requirement pending permits the alternative to be evaluated.
Calling Fail is stronger: it prevents overall success even if another handler calls Succeed. Do not confuse multiple requirements combined with AND with multiple handlers able to satisfy one requirement.
Handler state and ordering
Handler execution order must not be treated as a functional contract. If B needs data that A stores in a mutable field, composition changes can change the decision. Obtain premises through explicit dependencies and avoid storing user-specific decisions in a singleton.
The example handler is stateless and evaluates the supplied principal and resource. It also does not assume that merely being invoked proves successful authentication.
Resource ownership
Persisted document ownership is an authorization premise distinct from an OwnerId field sent by the client. Load the document from the authoritative source, evaluate AuthorizeAsync with the principal and resource, and only then construct the response disclosing content.
In the example, sub retains its original name because configuration assumes MapInboundClaims=false. In an application with different mapping, explicitly adjust the identity contract rather than trying arbitrary claims.
Decide before disclosure
A later 403 cannot recover bytes already received by a client. Keep access decisions before document serialization and transmission. The scenario of user-a requesting user-b’s document exposes the boundary: supplying OwnerId=user-a in the body does not change stored ownership.
The application still must choose whether to distinguish missing from forbidden resources and keep that contract consistent without exposing protected content in errors or logs.
// ASP.NET Core 10 authorization excerpt, not a complete host.
// Prerequisites: JWT bearer is configured and validates trusted issuer,
// audience, signature and lifetime. MapInboundClaims=false preserves "sub".
// Register policy and handler before building the host:
// services.AddAuthorization(options => options.AddPolicy("DocumentOwner",
// policy => policy.RequireAuthenticatedUser()
// .AddRequirements(new DocumentOwnerRequirement())));
// services.AddSingleton<IAuthorizationHandler, DocumentOwnerHandler>();
// Invoke ReadDocument with a document loaded from authoritative storage.
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
public sealed record Document(int Id, string OwnerId, string Content);
public sealed class DocumentOwnerRequirement : IAuthorizationRequirement { }
public sealed class DocumentOwnerHandler
: AuthorizationHandler<DocumentOwnerRequirement, Document>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
DocumentOwnerRequirement requirement, Document resource)
{
string? subject = context.User.FindFirst("sub")?.Value;
if (context.User.Identity?.IsAuthenticated == true &&
subject is not null && subject == resource.OwnerId)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
public static class DocumentResponse
{
public static async Task<IResult> ReadDocument(
ClaimsPrincipal user, Document storedDocument,
IAuthorizationService authorization)
{
var decision = await authorization.AuthorizeAsync(
user, storedDocument, "DocumentOwner");
if (decision.Succeeded)
return Results.Ok(new { storedDocument.Id, storedDocument.Content });
return user.Identity?.IsAuthenticated == true
? Results.Forbid() : Results.Challenge();
}
}Read the excerpt assuming bearer as the challenge and forbid scheme, one selected principal and an unmapped sub. For Document(42,"user-b",...), sub=user-a does not satisfy DocumentOwner; authenticated sub=user-b does. The caller loads the persisted resource and separately handles a missing document.
The excerpt does not configure JWT, a repository, an endpoint or a resource-existence concealment contract.
Common pitfalls
Decoding a JWT does not validate its signature. AllowAnonymous can make an endpoint public. A named policy does not inherit the fallback. Comparing a caller-supplied owner lets the caller choose the authorization premise.
Related topics: Routing, binding and validation · Test scope and integration evidence
Validate identity through the configured scheme, identify applicable requirements and decide over the actual resource before producing the response. Keep the contract for 401, 403 and missing resources explicit.
Reference: Authentication identity construction · ASP.NET Core 10.0 / .NET 10 / C# 14