Security in VSA follows the same principle as everything else: authorization is declared where the feature is. The endpoints file declares what roles can call the API; the controller declares what roles can view the pages; the handlers don't worry about authentication because the framework enforces it before a handler ever runs. This part shows the two production approaches — JWT for the Blazor CRM and cookie auth for the MVC Project Manager — and how roles and policies fit into each VSA slice.
Both applications use ASP.NET Core Identity with role-based authorization. The difference is the authentication scheme: the Blazor CRM issues JWT bearer tokens (ideal for its Blazor WebAssembly and API-driven architecture), while the MVC Project Manager uses cookie authentication (the natural fit for server-rendered Razor pages). Both schemes plug into the same authorization pipeline, so roles and policies work identically.
JWT Authorization at the Endpoint Group Level (Blazor CRM)
The Blazor CRM secures the entire Todo API in one place — on the MapGroup. Every endpoint under /api/todo inherits the JWT requirement automatically:
var group = app.MapGroup("/api/todo").WithTags("Todos")
.RequireAuthorization(policy => policy
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser());
This is VSA security in its purest form: the authorization rule lives in the feature's TodoEndpoint.cs file, right next to the endpoints it protects. Adding a new endpoint to the group automatically secures it — you can't accidentally forget to authorize a new route. The RequireAuthenticatedUser() is the baseline; role requirements layer on top.
Role-Based Endpoint Requirements
When an endpoint needs specific roles, add RequireAuthorization with role names directly on the endpoint. The MVC Project Manager restricts its Todo API to Admin and Member roles:
group.MapGet("/", async (IMediator mediator) =>
{
var result = await mediator.Send(new GetTodoListQuery());
return Results.Ok(result);
})
.RequireAuthorization(new AuthorizeAttribute
{
Roles = $"{ApplicationRoles.AdminConst},{ApplicationRoles.MemberConst}"
});
Placing the role requirement on the endpoint keeps the authorization rule visible at the point of exposure. If the business rule changes — say, only Admins can export — you edit the export endpoint's attribute, not the handler. The handler stays authorization-agnostic, which keeps it testable without authentication setup.
Cookie Authentication on MVC Controllers (MVC Project Manager)
The MVC Project Manager's TodoController uses cookie auth with [Authorize(Roles)] to protect the Razor pages:
[Area("Main")]
[Authorize(Roles = $"{ApplicationRoles.AdminConst},{ApplicationRoles.MemberConst}")]
public class TodoController : Controller
{
public IActionResult Index() => View();
public IActionResult Create() => View();
public IActionResult Edit(string id) => View();
public IActionResult Detail(string id) => View();
}
The class-level [Authorize(Roles)] protects all four actions — Index, Create, Edit, Detail. Unauthenticated users are redirected to the login page (cookie auth's default behavior), while the browser cookie is sent automatically with every request. The controller and the endpoint file sit side-by-side in the same feature folder, so the complete authorization story for the Todo feature is visible in two adjacent files.
Policy-Based Authorization
For more complex rules, policy-based authorization beats role strings. Define policies once in Program.cs and reference them by name in any slice:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("TodoAdmin", policy =>
policy.RequireRole(ApplicationRoles.AdminConst));
options.AddPolicy("CanManageTodos", policy =>
policy.RequireAssertion(ctx =>
ctx.User.IsInRole(ApplicationRoles.AdminConst) ||
ctx.User.HasClaim("permission", "todo.manage")));
});
// Usage in a slice endpoint
group.MapPost("/", async (CreateTodoRequest request, IMediator mediator) => { ... })
.RequireAuthorization("CanManageTodos");
Policies centralize the rule definition while leaving the enforcement point in the slice. The RequireAssertion form handles composite rules (role OR claim) that string concatenation can't express cleanly. This is the pattern to reach for as your authorization rules grow beyond simple role checks.
How Roles Flow Through Handlers
A common question in VSA is whether handlers should check roles. They shouldn't. Authorization runs as middleware before the request reaches the handler — the framework validates the JWT/cookie, verifies the role requirement, and rejects unauthorized requests with 401/403. By the time a handler executes, you can trust that the caller is authenticated and authorized. This separation means handlers are pure business logic, testable with a fake DbContext and no authentication infrastructure.
Protecting Both APIs and Views in One Slice
The MVC Project Manager's Todo feature demonstrates the complete picture: the controller protects the Razor views (Index, Create, Edit, Detail) with cookie auth, while the endpoint group protects the JSON API with role requirements. Both files live in the same feature folder, and both enforce the same roles. A developer auditing the Todo feature's security opens two files and sees the entire authorization surface. That's the VSA security contract: authorization is co-located with the feature, never scattered across a global security config.