LyraLearn AI Learning Platform
Exams
← Module 7 Β· Securing Public-Sector Web Apps
🎧 Listen

Authentication and Authorization in MVC

Security questions in a web app split into two that sound alike but are enforced in different places: authentication ("who are you?") and authorization ("what may you do?"). In a public-sector app holding candidate records, getting the second one wrong is the classic audit finding β€” so this lesson treats authorization as the main event.

Cookie authentication and Identity

The shape is standard: the user proves identity once (password, or the agency's single sign-on), the server issues an encrypted authentication cookie, and every subsequent request carries it. In ASP.NET Core this is the built-in cookie authentication handler, with ASP.NET Core Identity providing user/password storage, hashing, and lockout on top. The cookie materializes on each request as a ClaimsPrincipal β€” a bag of claims (user id, name, roles) available as User in controllers and views. Practical rules: keep session lifetime short for staff apps, always mark the cookie Secure and HttpOnly, and never invent your own cookie scheme.

[Authorize], roles, and policies

The [Authorize] attribute is the gate. Applied to a controller or action, it rejects anonymous users; with arguments it checks who the user is:

[Authorize(Roles = "CommissionAnalyst,Admin")]
public IActionResult ReviewQueue() { ... }

Roles ("Admin", "EppReviewer") are coarse-grained and fine for menu-level access. The better tool is policies β€” named requirements like RequireClaim("EppId") or custom IAuthorizationHandlers, registered once in Program.cs β€” which keep rules in one place instead of scattered strings, and compose ([Authorize(Policy = "SameEpp")]). Two habits matter: authorize by deny-by-default (a global fallback policy requiring authentication, with [AllowAnonymous] as the explicit exception), and never rely on hiding a link in the view β€” the URL still exists.

The check attributes can't do: record ownership

Roles answer "may reviewers open candidate records?" They cannot answer "may this reviewer open this candidate's record?" That's a per-record ownership check, and it must live in your service layer, next to the data:

var candidate = await _db.Candidates.FindAsync(id);
if (candidate is null || candidate.EppId != currentUser.EppId)
    return Forbid();

Skipping this is the OWASP Insecure Direct Object Reference bug: a reviewer from one EPP edits the URL from /Candidates/Details/482 to /483 and reads another program's student data. Put the ownership filter in the query itself (Where(c => c.EppId == user.EppId)) wherever possible, so forgetting the check returns nothing rather than someone else's record. Attribute for the door, query filter for the filing cabinet β€” you need both.

🧠 Quiz yourself on this lesson →

Ask the AI Tutor

Grounded in the course lessons β€” it cites its sources and says when it doesn't know.