MVCBeginnersSeptember 2026 · 5 min read

ASP.NET Core MVC Authentication vs Authorization: Protect Pages and APIs

By go2ismail · Published · .NET 10

TL;DR

Authentication establishes identity; authorization decides access. In an MVC page plus API design, protect both server entry points. Hiding an Admin link does not prevent direct calls to its API.

The foundation: what the official documentation explains

ASP.NET Core authentication uses schemes to establish a principal. Authorization evaluates access requirements, including roles and policies. The Authorize attribute can specify role requirements for a controller or action, while endpoint metadata can apply requirements to Minimal APIs. An unauthenticated request and an authenticated user lacking permission are different situations.

Implementation context: The examples use a .NET 10 MVC application organized into feature folders (Vertical Slice Architecture). Basic C# classes and async/await are assumed. Reference excerpts show selected parts of that application; separately labeled teaching adaptations explain alternatives. They are not complete standalone projects.

Find both entry points before adding a lock

The Country feature exposes MVC pages such as /Admin/Country/Index and JSON operations under /api/country. The controller and route group each declare an administrator role requirement. This is the practical consequence of the hybrid design: securing the screen is not a substitute for securing the data operations.

The following excerpts assume the existing Identity and authorization infrastructure. They are not a complete login implementation. For this lesson, begin with a local test account whose assigned roles you can inspect, plus another account that should not have administrator access.

Reference excerpt: Areas/Admin/Country/Controllers/CountryController.cs

[Area("Admin")]
[Authorize(Roles = ApplicationRoles.AdminConst)]
public class CountryController : Controller
{

Reference excerpt: Areas/Admin/Country/Endpoints/CountryEndpoint.cs

var group = app.MapGroup("/api/country")
            .WithTags("Countries")
            .RequireAuthorization(new AuthorizeAttribute { Roles = ApplicationRoles.AdminConst });

Do not confuse a role with a named policy

The route group uses an AuthorizeAttribute with its Roles property. That explicitly expresses a role requirement. By contrast, RequireAuthorization("Admin") refers to a named policy called Admin; it does not inherently mean membership in the Admin role. The strings can look identical while the API overloads mean different things.

A named policy is useful when you want to describe an operation and centralize its requirements. For example, a ManageCountries policy could require an administrator role. That would be a deliberate alternative to the role metadata shown here, with matching policy registration. Do not change only the call-site string and expect the same behavior.

Read scheme selection independently

The infrastructure registers JWT bearer handling in addition to Identity authentication. Its configured DefaultPolicy explicitly lists the Identity application scheme and bearer scheme. However, do not assume that merely defining a DefaultPolicy makes every explicit role or named policy use those schemes in the same way. Inspect the effective policy and scheme selection for the exact endpoint.

For a beginner working with this browser UI, start by tracing the authenticated page session and the API request it sends. If you add a separate bearer client, verify that the target endpoint actually selects the intended scheme and validates the token. A JWT settings block alone is not evidence that every route accepts bearer authentication.

Reference excerpt: Infrastructures/Authentications/Jwt/DI.cs

services.AddAuthorization(options =>
        {
            options.DefaultPolicy = new AuthorizationPolicyBuilder()
                .AddAuthenticationSchemes(
                    IdentityConstants.ApplicationScheme,
                    JwtBearerDefaults.AuthenticationScheme)
                .RequireAuthenticatedUser()
                .Build();
        });

Keep role names consistent

ApplicationRoles defines compile-time constants for attributes and separate runtime properties that can be initialized from configuration. Country uses AdminConst. If configuration changes a runtime role name while attributes still require the literal Admin constant, the two can diverge. Trace the stored membership and the actual requirement before concluding that authorization is broken.

A menu may hide an inaccessible link for usability, but a user can still type its URL or send a request with another client. The authoritative check belongs on the server. Likewise, accepting a role name from a form is not a reason to grant that role to the caller.

Verify an access matrix

In a local environment, test the page and API as an anonymous caller, an authenticated non-admin, and an admin. Verify that unauthorized calls do not reach the protected mutation handler. Distinguish the final HTTP response from an HTML redirect: scheme behavior and middleware influence what the browser sees.

Use harmless reads for initial access checks. If a write check is needed, use disposable data. Do not interpret a hidden button as a passed authorization test, and do not interpret a successful login as proof of administrator membership. Those observations answer different questions.

Keep this lesson focused

Role checks protect the feature as a whole. More detailed rules, such as only editing records owned by the caller, require resource-aware decisions inside the appropriate workflow. Antiforgery protection for cookie-authenticated writes is also a separate concern. Understanding these boundaries prevents a short authorization excerpt from being mistaken for a complete security design.

Key Takeaways

  • Protect MVC pages and API endpoints independently.
  • A role requirement is not the same as a policy name.
  • Check effective schemes and role names when access behaves unexpectedly.

FAQ

Does RequireAuthorization("Admin") automatically check the Admin role?

No. That overload names a policy. Use role metadata or register a policy with the intended role requirement.

Can I protect the API by hiding its page link?

No. API requests can be made directly, so the endpoint needs server-side authorization.

Why can a logged-in user still be denied?

Authentication proves identity. The user may still lack the required role or fail the effective policy.