[{"data":1,"prerenderedAt":1485},["ShallowReactive",2],{"blog-\u002Fblog\u002Fdotnet-authorization":3},{"id":4,"title":5,"body":6,"description":1470,"difficulty":1471,"extension":1472,"framework":1473,"frameworkSlug":1474,"meta":1475,"navigation":89,"order":68,"path":1476,"qaPath":1477,"seo":1478,"stem":1479,"subtopic":1480,"topic":1481,"topicSlug":1482,"updated":1483,"__hash__":1484},"blog\u002Fblog\u002Fdotnet-authorization.md","Policy-Based Authorization in ASP.NET Core",{"type":7,"value":8,"toc":1454},"minimark",[9,14,28,32,43,49,150,157,161,172,229,239,243,254,423,426,467,471,489,712,723,731,861,877,881,952,962,966,1009,1041,1045,1048,1077,1080,1084,1177,1187,1191,1206,1333,1343,1347,1429,1433,1450],[10,11,13],"h2",{"id":12},"why-authorization-knowledge-matters-in-net-interviews","Why authorization knowledge matters in .NET interviews",[15,16,17,18,22,23,27],"p",{},"Interviewers frequently distinguish candidates by whether they understand ",[19,20,21],"em",{},"just"," ",[24,25,26],"code",{},"[Authorize(Roles = \"Admin\")]"," or whether they can explain policies, requirements, handlers, and resource-based\nauthorization. This article covers the authorization model from simple role checks through to\ndynamic policy providers.",[10,29,31],{"id":30},"role-based-vs-policy-based-authorization","Role-based vs policy-based authorization",[15,33,34,38,39,42],{},[35,36,37],"strong",{},"Role-based"," authorization is a single claim check — is the user's ",[24,40,41],{},"ClaimTypes.Role"," claim in\nthe allowed set? It's simple and readable but becomes unmanageable as permission logic grows.",[15,44,45,48],{},[35,46,47],{},"Policy-based"," authorization composes any number of requirements into a named, reusable rule\nthat you define once and apply anywhere.",[50,51,56],"pre",{"className":52,"code":53,"language":54,"meta":55,"style":55},"language-csharp shiki shiki-themes github-light github-dark","\u002F\u002F Role-based — simple but scattered, hard to change later:\n[Authorize(Roles = \"Admin\")]\n[Authorize(Roles = \"Manager\")] \u002F\u002F stacked = AND\npublic class ManagerAdminController : ControllerBase { }\n\n\u002F\u002F Policy-based — centralized, composable, testable:\nbuilder.Services.AddAuthorizationBuilder()\n    .AddPolicy(\"SeniorEngineer\", policy =>\n        policy.RequireAuthenticatedUser()\n              .RequireRole(\"Employee\")\n              .RequireClaim(\"department\", \"Engineering\")\n              .AddRequirements(new MinimumTenureRequirement(years: 3)));\n\n[Authorize(Policy = \"SeniorEngineer\")]\npublic class SeniorPortalController : ControllerBase { }\n","csharp","",[24,57,58,66,72,78,84,91,97,103,109,115,121,127,133,138,144],{"__ignoreMap":55},[59,60,63],"span",{"class":61,"line":62},"line",1,[59,64,65],{},"\u002F\u002F Role-based — simple but scattered, hard to change later:\n",[59,67,69],{"class":61,"line":68},2,[59,70,71],{},"[Authorize(Roles = \"Admin\")]\n",[59,73,75],{"class":61,"line":74},3,[59,76,77],{},"[Authorize(Roles = \"Manager\")] \u002F\u002F stacked = AND\n",[59,79,81],{"class":61,"line":80},4,[59,82,83],{},"public class ManagerAdminController : ControllerBase { }\n",[59,85,87],{"class":61,"line":86},5,[59,88,90],{"emptyLinePlaceholder":89},true,"\n",[59,92,94],{"class":61,"line":93},6,[59,95,96],{},"\u002F\u002F Policy-based — centralized, composable, testable:\n",[59,98,100],{"class":61,"line":99},7,[59,101,102],{},"builder.Services.AddAuthorizationBuilder()\n",[59,104,106],{"class":61,"line":105},8,[59,107,108],{},"    .AddPolicy(\"SeniorEngineer\", policy =>\n",[59,110,112],{"class":61,"line":111},9,[59,113,114],{},"        policy.RequireAuthenticatedUser()\n",[59,116,118],{"class":61,"line":117},10,[59,119,120],{},"              .RequireRole(\"Employee\")\n",[59,122,124],{"class":61,"line":123},11,[59,125,126],{},"              .RequireClaim(\"department\", \"Engineering\")\n",[59,128,130],{"class":61,"line":129},12,[59,131,132],{},"              .AddRequirements(new MinimumTenureRequirement(years: 3)));\n",[59,134,136],{"class":61,"line":135},13,[59,137,90],{"emptyLinePlaceholder":89},[59,139,141],{"class":61,"line":140},14,[59,142,143],{},"[Authorize(Policy = \"SeniorEngineer\")]\n",[59,145,147],{"class":61,"line":146},15,[59,148,149],{},"public class SeniorPortalController : ControllerBase { }\n",[15,151,152,153,156],{},"Changing who qualifies as a \"SeniorEngineer\" means changing the policy definition in one place —\nnot hunting through every ",[24,154,155],{},"[Authorize]"," attribute in the codebase.",[10,158,160],{"id":159},"configuring-policies","Configuring policies",[15,162,163,164,167,168,171],{},"Use ",[24,165,166],{},"AddAuthorizationBuilder"," (.NET 8+) or ",[24,169,170],{},"AddAuthorization",":",[50,173,175],{"className":52,"code":174,"language":54,"meta":55,"style":55},"builder.Services.AddAuthorizationBuilder()\n    .AddPolicy(\"AtLeast18\",   policy => policy.Requirements.Add(new MinimumAgeRequirement(18)))\n    .AddPolicy(\"PremiumUser\", policy => policy.RequireAuthenticatedUser()\n                                              .RequireClaim(\"subscription\", \"premium\", \"enterprise\"))\n    .AddPolicy(\"AdminOnly\",   policy => policy.RequireRole(\"Admin\"))\n\n    \u002F\u002F Default policy — applied when [Authorize] has no parameters:\n    .SetDefaultPolicy(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build())\n\n    \u002F\u002F Fallback policy — applied to every endpoint with NO authorization metadata:\n    .SetFallbackPolicy(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());\n",[24,176,177,181,186,191,196,201,205,210,215,219,224],{"__ignoreMap":55},[59,178,179],{"class":61,"line":62},[59,180,102],{},[59,182,183],{"class":61,"line":68},[59,184,185],{},"    .AddPolicy(\"AtLeast18\",   policy => policy.Requirements.Add(new MinimumAgeRequirement(18)))\n",[59,187,188],{"class":61,"line":74},[59,189,190],{},"    .AddPolicy(\"PremiumUser\", policy => policy.RequireAuthenticatedUser()\n",[59,192,193],{"class":61,"line":80},[59,194,195],{},"                                              .RequireClaim(\"subscription\", \"premium\", \"enterprise\"))\n",[59,197,198],{"class":61,"line":86},[59,199,200],{},"    .AddPolicy(\"AdminOnly\",   policy => policy.RequireRole(\"Admin\"))\n",[59,202,203],{"class":61,"line":93},[59,204,90],{"emptyLinePlaceholder":89},[59,206,207],{"class":61,"line":99},[59,208,209],{},"    \u002F\u002F Default policy — applied when [Authorize] has no parameters:\n",[59,211,212],{"class":61,"line":105},[59,213,214],{},"    .SetDefaultPolicy(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build())\n",[59,216,217],{"class":61,"line":111},[59,218,90],{"emptyLinePlaceholder":89},[59,220,221],{"class":61,"line":117},[59,222,223],{},"    \u002F\u002F Fallback policy — applied to every endpoint with NO authorization metadata:\n",[59,225,226],{"class":61,"line":123},[59,227,228],{},"    .SetFallbackPolicy(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());\n",[15,230,231,232,235,236,238],{},"With a fallback policy, every route is protected unless you explicitly add ",[24,233,234],{},".AllowAnonymous()",".\nThis is the \"secure by default\" pattern — easier than hoping developers remember ",[24,237,155],{},".",[10,240,242],{"id":241},"requirements-and-handlers-separation-of-concerns","Requirements and handlers — separation of concerns",[15,244,245,246,249,250,253],{},"An ",[24,247,248],{},"IAuthorizationRequirement"," is a plain data object describing what must be true. An\n",[24,251,252],{},"IAuthorizationHandler"," contains the evaluation logic. Separating them makes handlers independently\ntestable.",[50,255,257],{"className":52,"code":256,"language":54,"meta":55,"style":55},"\u002F\u002F Requirement — data only:\npublic class MinimumAgeRequirement : IAuthorizationRequirement\n{\n    public int MinimumAge { get; }\n    public MinimumAgeRequirement(int age) => MinimumAge = age;\n}\n\n\u002F\u002F Handler — all logic here:\npublic class MinimumAgeHandler : AuthorizationHandler\u003CMinimumAgeRequirement>\n{\n    protected override Task HandleRequirementAsync(\n        AuthorizationHandlerContext context,\n        MinimumAgeRequirement requirement)\n    {\n        var dob = context.User.FindFirstValue(ClaimTypes.DateOfBirth);\n\n        if (dob is null)\n            return Task.CompletedTask; \u002F\u002F don't Fail — another handler might satisfy this\n\n        var age = DateTime.Today.Year - DateTime.Parse(dob).Year;\n\n        if (age >= requirement.MinimumAge)\n            context.Succeed(requirement);\n        else\n            context.Fail(); \u002F\u002F explicit deny\n        return Task.CompletedTask;\n    }\n}\n\n\u002F\u002F Register:\nbuilder.Services.AddSingleton\u003CIAuthorizationHandler, MinimumAgeHandler>();\n",[24,258,259,264,269,274,279,284,289,293,298,303,307,312,317,322,327,332,337,343,349,354,360,365,371,377,383,389,395,401,406,411,417],{"__ignoreMap":55},[59,260,261],{"class":61,"line":62},[59,262,263],{},"\u002F\u002F Requirement — data only:\n",[59,265,266],{"class":61,"line":68},[59,267,268],{},"public class MinimumAgeRequirement : IAuthorizationRequirement\n",[59,270,271],{"class":61,"line":74},[59,272,273],{},"{\n",[59,275,276],{"class":61,"line":80},[59,277,278],{},"    public int MinimumAge { get; }\n",[59,280,281],{"class":61,"line":86},[59,282,283],{},"    public MinimumAgeRequirement(int age) => MinimumAge = age;\n",[59,285,286],{"class":61,"line":93},[59,287,288],{},"}\n",[59,290,291],{"class":61,"line":99},[59,292,90],{"emptyLinePlaceholder":89},[59,294,295],{"class":61,"line":105},[59,296,297],{},"\u002F\u002F Handler — all logic here:\n",[59,299,300],{"class":61,"line":111},[59,301,302],{},"public class MinimumAgeHandler : AuthorizationHandler\u003CMinimumAgeRequirement>\n",[59,304,305],{"class":61,"line":117},[59,306,273],{},[59,308,309],{"class":61,"line":123},[59,310,311],{},"    protected override Task HandleRequirementAsync(\n",[59,313,314],{"class":61,"line":129},[59,315,316],{},"        AuthorizationHandlerContext context,\n",[59,318,319],{"class":61,"line":135},[59,320,321],{},"        MinimumAgeRequirement requirement)\n",[59,323,324],{"class":61,"line":140},[59,325,326],{},"    {\n",[59,328,329],{"class":61,"line":146},[59,330,331],{},"        var dob = context.User.FindFirstValue(ClaimTypes.DateOfBirth);\n",[59,333,335],{"class":61,"line":334},16,[59,336,90],{"emptyLinePlaceholder":89},[59,338,340],{"class":61,"line":339},17,[59,341,342],{},"        if (dob is null)\n",[59,344,346],{"class":61,"line":345},18,[59,347,348],{},"            return Task.CompletedTask; \u002F\u002F don't Fail — another handler might satisfy this\n",[59,350,352],{"class":61,"line":351},19,[59,353,90],{"emptyLinePlaceholder":89},[59,355,357],{"class":61,"line":356},20,[59,358,359],{},"        var age = DateTime.Today.Year - DateTime.Parse(dob).Year;\n",[59,361,363],{"class":61,"line":362},21,[59,364,90],{"emptyLinePlaceholder":89},[59,366,368],{"class":61,"line":367},22,[59,369,370],{},"        if (age >= requirement.MinimumAge)\n",[59,372,374],{"class":61,"line":373},23,[59,375,376],{},"            context.Succeed(requirement);\n",[59,378,380],{"class":61,"line":379},24,[59,381,382],{},"        else\n",[59,384,386],{"class":61,"line":385},25,[59,387,388],{},"            context.Fail(); \u002F\u002F explicit deny\n",[59,390,392],{"class":61,"line":391},26,[59,393,394],{},"        return Task.CompletedTask;\n",[59,396,398],{"class":61,"line":397},27,[59,399,400],{},"    }\n",[59,402,404],{"class":61,"line":403},28,[59,405,288],{},[59,407,409],{"class":61,"line":408},29,[59,410,90],{"emptyLinePlaceholder":89},[59,412,414],{"class":61,"line":413},30,[59,415,416],{},"\u002F\u002F Register:\n",[59,418,420],{"class":61,"line":419},31,[59,421,422],{},"builder.Services.AddSingleton\u003CIAuthorizationHandler, MinimumAgeHandler>();\n",[15,424,425],{},"Key rules:",[427,428,429,437,447,454,464],"ul",{},[430,431,432,433,436],"li",{},"Call ",[24,434,435],{},"context.Succeed(requirement)"," to mark the requirement as satisfied.",[430,438,432,439,442,443,446],{},[24,440,441],{},"context.Fail()"," to explicitly deny — overrides any other ",[24,444,445],{},"Succeed"," call.",[430,448,449,450,453],{},"Return ",[24,451,452],{},"Task.CompletedTask"," without calling either when the handler doesn't apply (gives other handlers a chance).",[430,455,456,457,460,461,463],{},"One requirement can have ",[35,458,459],{},"multiple handlers"," — any ",[24,462,445],{}," passes the requirement.",[430,465,466],{},"All requirements in a policy must be satisfied for the policy to pass.",[10,468,470],{"id":469},"resource-based-authorization","Resource-based authorization",[15,472,473,474,477,478,481,482,484,485,488],{},"Sometimes the authorization decision depends on the resource being accessed — \"can ",[19,475,476],{},"this user","\nedit ",[19,479,480],{},"this document","?\" The resource isn't known at decoration time, so ",[24,483,155],{}," alone\ncan't express it. Use ",[24,486,487],{},"IAuthorizationService"," in the controller or service:",[50,490,492],{"className":52,"code":491,"language":54,"meta":55,"style":55},"\u002F\u002F Operation-based requirement:\npublic static class DocumentOperations\n{\n    public static OperationAuthorizationRequirement Read   = new() { Name = \"Read\" };\n    public static OperationAuthorizationRequirement Edit   = new() { Name = \"Edit\" };\n    public static OperationAuthorizationRequirement Delete = new() { Name = \"Delete\" };\n}\n\n\u002F\u002F Handler receives the resource instance:\npublic class DocumentAuthorizationHandler\n    : AuthorizationHandler\u003COperationAuthorizationRequirement, Document>\n{\n    protected override Task HandleRequirementAsync(\n        AuthorizationHandlerContext context,\n        OperationAuthorizationRequirement requirement,\n        Document document)\n    {\n        var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);\n\n        if (document.IsPublic && requirement.Name == \"Read\")\n            context.Succeed(requirement);\n        else if (document.OwnerId == userId)\n            context.Succeed(requirement);\n        else if (requirement.Name != \"Delete\" && context.User.IsInRole(\"Editor\"))\n            context.Succeed(requirement);\n\n        return Task.CompletedTask;\n    }\n}\n\nbuilder.Services.AddSingleton\u003CIAuthorizationHandler, DocumentAuthorizationHandler>();\n\n\u002F\u002F Controller — inject IAuthorizationService:\n[HttpPut(\"{id}\")]\npublic async Task\u003CIActionResult> UpdateDocument(int id, [FromBody] UpdateDto dto)\n{\n    var document = await _repo.GetByIdAsync(id);\n    if (document is null) return NotFound();\n\n    var authResult = await _authorizationService.AuthorizeAsync(User, document, DocumentOperations.Edit);\n    if (!authResult.Succeeded) return Forbid();\n\n    await _repo.UpdateAsync(document, dto);\n    return NoContent();\n}\n",[24,493,494,499,504,508,513,518,523,527,531,536,541,546,550,554,558,563,568,572,577,581,586,590,595,599,604,608,612,616,620,624,628,633,638,644,650,656,661,667,673,678,684,690,695,701,707],{"__ignoreMap":55},[59,495,496],{"class":61,"line":62},[59,497,498],{},"\u002F\u002F Operation-based requirement:\n",[59,500,501],{"class":61,"line":68},[59,502,503],{},"public static class DocumentOperations\n",[59,505,506],{"class":61,"line":74},[59,507,273],{},[59,509,510],{"class":61,"line":80},[59,511,512],{},"    public static OperationAuthorizationRequirement Read   = new() { Name = \"Read\" };\n",[59,514,515],{"class":61,"line":86},[59,516,517],{},"    public static OperationAuthorizationRequirement Edit   = new() { Name = \"Edit\" };\n",[59,519,520],{"class":61,"line":93},[59,521,522],{},"    public static OperationAuthorizationRequirement Delete = new() { Name = \"Delete\" };\n",[59,524,525],{"class":61,"line":99},[59,526,288],{},[59,528,529],{"class":61,"line":105},[59,530,90],{"emptyLinePlaceholder":89},[59,532,533],{"class":61,"line":111},[59,534,535],{},"\u002F\u002F Handler receives the resource instance:\n",[59,537,538],{"class":61,"line":117},[59,539,540],{},"public class DocumentAuthorizationHandler\n",[59,542,543],{"class":61,"line":123},[59,544,545],{},"    : AuthorizationHandler\u003COperationAuthorizationRequirement, Document>\n",[59,547,548],{"class":61,"line":129},[59,549,273],{},[59,551,552],{"class":61,"line":135},[59,553,311],{},[59,555,556],{"class":61,"line":140},[59,557,316],{},[59,559,560],{"class":61,"line":146},[59,561,562],{},"        OperationAuthorizationRequirement requirement,\n",[59,564,565],{"class":61,"line":334},[59,566,567],{},"        Document document)\n",[59,569,570],{"class":61,"line":339},[59,571,326],{},[59,573,574],{"class":61,"line":345},[59,575,576],{},"        var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);\n",[59,578,579],{"class":61,"line":351},[59,580,90],{"emptyLinePlaceholder":89},[59,582,583],{"class":61,"line":356},[59,584,585],{},"        if (document.IsPublic && requirement.Name == \"Read\")\n",[59,587,588],{"class":61,"line":362},[59,589,376],{},[59,591,592],{"class":61,"line":367},[59,593,594],{},"        else if (document.OwnerId == userId)\n",[59,596,597],{"class":61,"line":373},[59,598,376],{},[59,600,601],{"class":61,"line":379},[59,602,603],{},"        else if (requirement.Name != \"Delete\" && context.User.IsInRole(\"Editor\"))\n",[59,605,606],{"class":61,"line":385},[59,607,376],{},[59,609,610],{"class":61,"line":391},[59,611,90],{"emptyLinePlaceholder":89},[59,613,614],{"class":61,"line":397},[59,615,394],{},[59,617,618],{"class":61,"line":403},[59,619,400],{},[59,621,622],{"class":61,"line":408},[59,623,288],{},[59,625,626],{"class":61,"line":413},[59,627,90],{"emptyLinePlaceholder":89},[59,629,630],{"class":61,"line":419},[59,631,632],{},"builder.Services.AddSingleton\u003CIAuthorizationHandler, DocumentAuthorizationHandler>();\n",[59,634,636],{"class":61,"line":635},32,[59,637,90],{"emptyLinePlaceholder":89},[59,639,641],{"class":61,"line":640},33,[59,642,643],{},"\u002F\u002F Controller — inject IAuthorizationService:\n",[59,645,647],{"class":61,"line":646},34,[59,648,649],{},"[HttpPut(\"{id}\")]\n",[59,651,653],{"class":61,"line":652},35,[59,654,655],{},"public async Task\u003CIActionResult> UpdateDocument(int id, [FromBody] UpdateDto dto)\n",[59,657,659],{"class":61,"line":658},36,[59,660,273],{},[59,662,664],{"class":61,"line":663},37,[59,665,666],{},"    var document = await _repo.GetByIdAsync(id);\n",[59,668,670],{"class":61,"line":669},38,[59,671,672],{},"    if (document is null) return NotFound();\n",[59,674,676],{"class":61,"line":675},39,[59,677,90],{"emptyLinePlaceholder":89},[59,679,681],{"class":61,"line":680},40,[59,682,683],{},"    var authResult = await _authorizationService.AuthorizeAsync(User, document, DocumentOperations.Edit);\n",[59,685,687],{"class":61,"line":686},41,[59,688,689],{},"    if (!authResult.Succeeded) return Forbid();\n",[59,691,693],{"class":61,"line":692},42,[59,694,90],{"emptyLinePlaceholder":89},[59,696,698],{"class":61,"line":697},43,[59,699,700],{},"    await _repo.UpdateAsync(document, dto);\n",[59,702,704],{"class":61,"line":703},44,[59,705,706],{},"    return NoContent();\n",[59,708,710],{"class":61,"line":709},45,[59,711,288],{},[15,713,714,715,718,719,722],{},"Rule: return ",[24,716,717],{},"NotFound()"," before ",[24,720,721],{},"Forbid()"," when the resource doesn't exist — leaking whether\na resource exists can be an information-disclosure vulnerability.",[10,724,726,727,730],{"id":725},"the-authorize-attribute-in-depth","The ",[59,728,729],{},"Authorize"," attribute in depth",[50,732,734],{"className":52,"code":733,"language":54,"meta":55,"style":55},"\u002F\u002F Bare — uses the default policy (requires IsAuthenticated):\n[Authorize]\npublic IActionResult Profile() => Ok(User.Identity!.Name);\n\n\u002F\u002F Role OR logic (comma-separated within one attribute):\n[Authorize(Roles = \"Admin,SuperAdmin\")]\n\n\u002F\u002F Role AND logic (stacked attributes — both must pass):\n[Authorize(Roles = \"Admin\")]\n[Authorize(Roles = \"Manager\")]\n\n\u002F\u002F Named policy:\n[Authorize(Policy = \"PremiumUser\")]\n\n\u002F\u002F Specific authentication scheme:\n[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]\n\n\u002F\u002F Controller-level + per-action override:\n[Authorize]\npublic class AccountController : ControllerBase\n{\n    [AllowAnonymous]             \u002F\u002F overrides controller-level auth — open to everyone\n    public IActionResult Login() => Ok();\n\n    [Authorize(Roles = \"Admin\")] \u002F\u002F requires auth + Admin role\n    public IActionResult Admin() => Ok();\n}\n",[24,735,736,741,746,751,755,760,765,769,774,778,783,787,792,797,801,806,811,815,820,824,829,833,838,843,847,852,857],{"__ignoreMap":55},[59,737,738],{"class":61,"line":62},[59,739,740],{},"\u002F\u002F Bare — uses the default policy (requires IsAuthenticated):\n",[59,742,743],{"class":61,"line":68},[59,744,745],{},"[Authorize]\n",[59,747,748],{"class":61,"line":74},[59,749,750],{},"public IActionResult Profile() => Ok(User.Identity!.Name);\n",[59,752,753],{"class":61,"line":80},[59,754,90],{"emptyLinePlaceholder":89},[59,756,757],{"class":61,"line":86},[59,758,759],{},"\u002F\u002F Role OR logic (comma-separated within one attribute):\n",[59,761,762],{"class":61,"line":93},[59,763,764],{},"[Authorize(Roles = \"Admin,SuperAdmin\")]\n",[59,766,767],{"class":61,"line":99},[59,768,90],{"emptyLinePlaceholder":89},[59,770,771],{"class":61,"line":105},[59,772,773],{},"\u002F\u002F Role AND logic (stacked attributes — both must pass):\n",[59,775,776],{"class":61,"line":111},[59,777,71],{},[59,779,780],{"class":61,"line":117},[59,781,782],{},"[Authorize(Roles = \"Manager\")]\n",[59,784,785],{"class":61,"line":123},[59,786,90],{"emptyLinePlaceholder":89},[59,788,789],{"class":61,"line":129},[59,790,791],{},"\u002F\u002F Named policy:\n",[59,793,794],{"class":61,"line":135},[59,795,796],{},"[Authorize(Policy = \"PremiumUser\")]\n",[59,798,799],{"class":61,"line":140},[59,800,90],{"emptyLinePlaceholder":89},[59,802,803],{"class":61,"line":146},[59,804,805],{},"\u002F\u002F Specific authentication scheme:\n",[59,807,808],{"class":61,"line":334},[59,809,810],{},"[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]\n",[59,812,813],{"class":61,"line":339},[59,814,90],{"emptyLinePlaceholder":89},[59,816,817],{"class":61,"line":345},[59,818,819],{},"\u002F\u002F Controller-level + per-action override:\n",[59,821,822],{"class":61,"line":351},[59,823,745],{},[59,825,826],{"class":61,"line":356},[59,827,828],{},"public class AccountController : ControllerBase\n",[59,830,831],{"class":61,"line":362},[59,832,273],{},[59,834,835],{"class":61,"line":367},[59,836,837],{},"    [AllowAnonymous]             \u002F\u002F overrides controller-level auth — open to everyone\n",[59,839,840],{"class":61,"line":373},[59,841,842],{},"    public IActionResult Login() => Ok();\n",[59,844,845],{"class":61,"line":379},[59,846,90],{"emptyLinePlaceholder":89},[59,848,849],{"class":61,"line":385},[59,850,851],{},"    [Authorize(Roles = \"Admin\")] \u002F\u002F requires auth + Admin role\n",[59,853,854],{"class":61,"line":391},[59,855,856],{},"    public IActionResult Admin() => Ok();\n",[59,858,859],{"class":61,"line":397},[59,860,288],{},[15,862,863,864,866,867,870,871,873,874,876],{},"Best practice: apply ",[24,865,155],{}," at the controller level and use ",[24,868,869],{},"[AllowAnonymous]"," on\nexceptions. The inverse (controller ",[24,872,869],{},", action ",[24,875,155],{},") makes it easy to\naccidentally expose an action by forgetting the attribute.",[10,878,880],{"id":879},"claims-based-authorization","Claims-based authorization",[50,882,884],{"className":52,"code":883,"language":54,"meta":55,"style":55},"builder.Services.AddAuthorizationBuilder()\n    \u002F\u002F Require the claim to exist with any value:\n    .AddPolicy(\"VerifiedEmail\", policy => policy.RequireClaim(ClaimTypes.Email))\n\n    \u002F\u002F Require the claim to have one of the listed values (OR):\n    .AddPolicy(\"EURegion\", policy => policy.RequireClaim(\"region\", \"EU\", \"EEA\", \"CH\"))\n\n    \u002F\u002F Inline lambda for one-off logic:\n    .AddPolicy(\"PremiumOrTrial\", policy => policy.RequireAssertion(ctx =>\n    {\n        var sub   = ctx.User.FindFirstValue(\"subscription\");\n        var trial = ctx.User.FindFirstValue(\"trial_active\");\n        return sub == \"premium\" || trial == \"true\";\n    }));\n",[24,885,886,890,895,900,904,909,914,918,923,928,932,937,942,947],{"__ignoreMap":55},[59,887,888],{"class":61,"line":62},[59,889,102],{},[59,891,892],{"class":61,"line":68},[59,893,894],{},"    \u002F\u002F Require the claim to exist with any value:\n",[59,896,897],{"class":61,"line":74},[59,898,899],{},"    .AddPolicy(\"VerifiedEmail\", policy => policy.RequireClaim(ClaimTypes.Email))\n",[59,901,902],{"class":61,"line":80},[59,903,90],{"emptyLinePlaceholder":89},[59,905,906],{"class":61,"line":86},[59,907,908],{},"    \u002F\u002F Require the claim to have one of the listed values (OR):\n",[59,910,911],{"class":61,"line":93},[59,912,913],{},"    .AddPolicy(\"EURegion\", policy => policy.RequireClaim(\"region\", \"EU\", \"EEA\", \"CH\"))\n",[59,915,916],{"class":61,"line":99},[59,917,90],{"emptyLinePlaceholder":89},[59,919,920],{"class":61,"line":105},[59,921,922],{},"    \u002F\u002F Inline lambda for one-off logic:\n",[59,924,925],{"class":61,"line":111},[59,926,927],{},"    .AddPolicy(\"PremiumOrTrial\", policy => policy.RequireAssertion(ctx =>\n",[59,929,930],{"class":61,"line":117},[59,931,326],{},[59,933,934],{"class":61,"line":123},[59,935,936],{},"        var sub   = ctx.User.FindFirstValue(\"subscription\");\n",[59,938,939],{"class":61,"line":129},[59,940,941],{},"        var trial = ctx.User.FindFirstValue(\"trial_active\");\n",[59,943,944],{"class":61,"line":135},[59,945,946],{},"        return sub == \"premium\" || trial == \"true\";\n",[59,948,949],{"class":61,"line":140},[59,950,951],{},"    }));\n",[15,953,163,954,957,958,961],{},[24,955,956],{},"RequireClaim"," for attribute-style checks. Use ",[24,959,960],{},"RequireAssertion"," for simple inline logic that\ndoesn't justify a full requirement+handler pair. Move complex, reusable logic into proper handlers.",[10,963,965],{"id":964},"default-and-fallback-policies","Default and fallback policies",[967,968,969,982],"table",{},[970,971,972],"thead",{},[973,974,975,979],"tr",{},[976,977,978],"th",{},"Policy",[976,980,981],{},"When applied",[983,984,985,997],"tbody",{},[973,986,987,991],{},[988,989,990],"td",{},"Default policy",[988,992,993,994,996],{},"When ",[24,995,155],{}," is used with no parameters",[973,998,999,1002],{},[988,1000,1001],{},"Fallback policy",[988,1003,1004,1005,1008],{},"For every endpoint with ",[35,1006,1007],{},"no"," authorization metadata",[50,1010,1012],{"className":52,"code":1011,"language":54,"meta":55,"style":55},"\u002F\u002F Secure-by-default: protect everything; opt out with AllowAnonymous:\nbuilder.Services.AddAuthorizationBuilder()\n    .SetFallbackPolicy(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());\n\napp.MapGet(\"\u002Fproducts\", (IProductService svc) => svc.GetAll()); \u002F\u002F protected by fallback\napp.MapGet(\"\u002Fhealth\",   () => Results.Ok()).AllowAnonymous();    \u002F\u002F explicitly public\n",[24,1013,1014,1019,1023,1027,1031,1036],{"__ignoreMap":55},[59,1015,1016],{"class":61,"line":62},[59,1017,1018],{},"\u002F\u002F Secure-by-default: protect everything; opt out with AllowAnonymous:\n",[59,1020,1021],{"class":61,"line":68},[59,1022,102],{},[59,1024,1025],{"class":61,"line":74},[59,1026,228],{},[59,1028,1029],{"class":61,"line":80},[59,1030,90],{"emptyLinePlaceholder":89},[59,1032,1033],{"class":61,"line":86},[59,1034,1035],{},"app.MapGet(\"\u002Fproducts\", (IProductService svc) => svc.GetAll()); \u002F\u002F protected by fallback\n",[59,1037,1038],{"class":61,"line":93},[59,1039,1040],{},"app.MapGet(\"\u002Fhealth\",   () => Results.Ok()).AllowAnonymous();    \u002F\u002F explicitly public\n",[10,1042,1044],{"id":1043},"global-authorization-filter","Global authorization filter",[15,1046,1047],{},"For controller-based apps that don't use a fallback policy:",[50,1049,1051],{"className":52,"code":1050,"language":54,"meta":55,"style":55},"builder.Services.AddControllers(options =>\n{\n    var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();\n    options.Filters.Add(new AuthorizeFilter(policy));\n});\n",[24,1052,1053,1058,1062,1067,1072],{"__ignoreMap":55},[59,1054,1055],{"class":61,"line":62},[59,1056,1057],{},"builder.Services.AddControllers(options =>\n",[59,1059,1060],{"class":61,"line":68},[59,1061,273],{},[59,1063,1064],{"class":61,"line":74},[59,1065,1066],{},"    var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();\n",[59,1068,1069],{"class":61,"line":80},[59,1070,1071],{},"    options.Filters.Add(new AuthorizeFilter(policy));\n",[59,1073,1074],{"class":61,"line":86},[59,1075,1076],{},"});\n",[15,1078,1079],{},"The fallback policy approach is preferred because it covers both controllers and minimal API\nendpoints with one line.",[10,1081,1083],{"id":1082},"iauthorizationservice-imperative-checks","IAuthorizationService — imperative checks",[50,1085,1087],{"className":52,"code":1086,"language":54,"meta":55,"style":55},"\u002F\u002F Inject into controllers or services:\npublic class ReportController : ControllerBase\n{\n    private readonly IAuthorizationService _authz;\n    public ReportController(IAuthorizationService authz) => _authz = authz;\n\n    [HttpGet(\"{id}\")]\n    public async Task\u003CIActionResult> GetReport(int id)\n    {\n        var report = await _reportRepo.GetAsync(id);\n        if (report is null) return NotFound();\n\n        \u002F\u002F Resource-based check — policy name:\n        var result = await _authz.AuthorizeAsync(User, report, \"CanViewReports\");\n        if (!result.Succeeded) return Forbid();\n\n        return Ok(report);\n    }\n}\n",[24,1088,1089,1094,1099,1103,1108,1113,1117,1122,1127,1131,1136,1141,1145,1150,1155,1160,1164,1169,1173],{"__ignoreMap":55},[59,1090,1091],{"class":61,"line":62},[59,1092,1093],{},"\u002F\u002F Inject into controllers or services:\n",[59,1095,1096],{"class":61,"line":68},[59,1097,1098],{},"public class ReportController : ControllerBase\n",[59,1100,1101],{"class":61,"line":74},[59,1102,273],{},[59,1104,1105],{"class":61,"line":80},[59,1106,1107],{},"    private readonly IAuthorizationService _authz;\n",[59,1109,1110],{"class":61,"line":86},[59,1111,1112],{},"    public ReportController(IAuthorizationService authz) => _authz = authz;\n",[59,1114,1115],{"class":61,"line":93},[59,1116,90],{"emptyLinePlaceholder":89},[59,1118,1119],{"class":61,"line":99},[59,1120,1121],{},"    [HttpGet(\"{id}\")]\n",[59,1123,1124],{"class":61,"line":105},[59,1125,1126],{},"    public async Task\u003CIActionResult> GetReport(int id)\n",[59,1128,1129],{"class":61,"line":111},[59,1130,326],{},[59,1132,1133],{"class":61,"line":117},[59,1134,1135],{},"        var report = await _reportRepo.GetAsync(id);\n",[59,1137,1138],{"class":61,"line":123},[59,1139,1140],{},"        if (report is null) return NotFound();\n",[59,1142,1143],{"class":61,"line":129},[59,1144,90],{"emptyLinePlaceholder":89},[59,1146,1147],{"class":61,"line":135},[59,1148,1149],{},"        \u002F\u002F Resource-based check — policy name:\n",[59,1151,1152],{"class":61,"line":140},[59,1153,1154],{},"        var result = await _authz.AuthorizeAsync(User, report, \"CanViewReports\");\n",[59,1156,1157],{"class":61,"line":146},[59,1158,1159],{},"        if (!result.Succeeded) return Forbid();\n",[59,1161,1162],{"class":61,"line":334},[59,1163,90],{"emptyLinePlaceholder":89},[59,1165,1166],{"class":61,"line":339},[59,1167,1168],{},"        return Ok(report);\n",[59,1170,1171],{"class":61,"line":345},[59,1172,400],{},[59,1174,1175],{"class":61,"line":351},[59,1176,288],{},[15,1178,163,1179,1182,1183,1186],{},[24,1180,1181],{},"[Authorize(Policy = \"...\")]"," for static checks. Use ",[24,1184,1185],{},"IAuthorizationService.AuthorizeAsync","\nwhen the check depends on the resource instance.",[10,1188,1190],{"id":1189},"dynamic-policies-with-iauthorizationpolicyprovider","Dynamic policies with IAuthorizationPolicyProvider",[15,1192,1193,1194,1197,1198,1201,1202,1205],{},"When you have a family of policies that vary only by parameter (e.g., ",[24,1195,1196],{},"MinimumAge:18",",\n",[24,1199,1200],{},"MinimumAge:21","), implement ",[24,1203,1204],{},"IAuthorizationPolicyProvider"," to generate them on demand:",[50,1207,1209],{"className":52,"code":1208,"language":54,"meta":55,"style":55},"public class MinimumAgePolicyProvider : IAuthorizationPolicyProvider\n{\n    private const string Prefix = \"MinimumAge:\";\n    private readonly DefaultAuthorizationPolicyProvider _fallback;\n\n    public MinimumAgePolicyProvider(IOptions\u003CAuthorizationOptions> options)\n        => _fallback = new DefaultAuthorizationPolicyProvider(options);\n\n    public Task\u003CAuthorizationPolicy?> GetPolicyAsync(string policyName)\n    {\n        if (policyName.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase)\n            && int.TryParse(policyName[Prefix.Length..], out var age))\n        {\n            var policy = new AuthorizationPolicyBuilder()\n                .AddRequirements(new MinimumAgeRequirement(age))\n                .Build();\n            return Task.FromResult\u003CAuthorizationPolicy?>(policy);\n        }\n        return _fallback.GetPolicyAsync(policyName);\n    }\n\n    public Task\u003CAuthorizationPolicy> GetDefaultPolicyAsync()  => _fallback.GetDefaultPolicyAsync();\n    public Task\u003CAuthorizationPolicy?> GetFallbackPolicyAsync() => _fallback.GetFallbackPolicyAsync();\n}\n\nbuilder.Services.AddSingleton\u003CIAuthorizationPolicyProvider, MinimumAgePolicyProvider>();\n",[24,1210,1211,1216,1220,1225,1230,1234,1239,1244,1248,1253,1257,1262,1267,1272,1277,1282,1287,1292,1297,1302,1306,1310,1315,1320,1324,1328],{"__ignoreMap":55},[59,1212,1213],{"class":61,"line":62},[59,1214,1215],{},"public class MinimumAgePolicyProvider : IAuthorizationPolicyProvider\n",[59,1217,1218],{"class":61,"line":68},[59,1219,273],{},[59,1221,1222],{"class":61,"line":74},[59,1223,1224],{},"    private const string Prefix = \"MinimumAge:\";\n",[59,1226,1227],{"class":61,"line":80},[59,1228,1229],{},"    private readonly DefaultAuthorizationPolicyProvider _fallback;\n",[59,1231,1232],{"class":61,"line":86},[59,1233,90],{"emptyLinePlaceholder":89},[59,1235,1236],{"class":61,"line":93},[59,1237,1238],{},"    public MinimumAgePolicyProvider(IOptions\u003CAuthorizationOptions> options)\n",[59,1240,1241],{"class":61,"line":99},[59,1242,1243],{},"        => _fallback = new DefaultAuthorizationPolicyProvider(options);\n",[59,1245,1246],{"class":61,"line":105},[59,1247,90],{"emptyLinePlaceholder":89},[59,1249,1250],{"class":61,"line":111},[59,1251,1252],{},"    public Task\u003CAuthorizationPolicy?> GetPolicyAsync(string policyName)\n",[59,1254,1255],{"class":61,"line":117},[59,1256,326],{},[59,1258,1259],{"class":61,"line":123},[59,1260,1261],{},"        if (policyName.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase)\n",[59,1263,1264],{"class":61,"line":129},[59,1265,1266],{},"            && int.TryParse(policyName[Prefix.Length..], out var age))\n",[59,1268,1269],{"class":61,"line":135},[59,1270,1271],{},"        {\n",[59,1273,1274],{"class":61,"line":140},[59,1275,1276],{},"            var policy = new AuthorizationPolicyBuilder()\n",[59,1278,1279],{"class":61,"line":146},[59,1280,1281],{},"                .AddRequirements(new MinimumAgeRequirement(age))\n",[59,1283,1284],{"class":61,"line":334},[59,1285,1286],{},"                .Build();\n",[59,1288,1289],{"class":61,"line":339},[59,1290,1291],{},"            return Task.FromResult\u003CAuthorizationPolicy?>(policy);\n",[59,1293,1294],{"class":61,"line":345},[59,1295,1296],{},"        }\n",[59,1298,1299],{"class":61,"line":351},[59,1300,1301],{},"        return _fallback.GetPolicyAsync(policyName);\n",[59,1303,1304],{"class":61,"line":356},[59,1305,400],{},[59,1307,1308],{"class":61,"line":362},[59,1309,90],{"emptyLinePlaceholder":89},[59,1311,1312],{"class":61,"line":367},[59,1313,1314],{},"    public Task\u003CAuthorizationPolicy> GetDefaultPolicyAsync()  => _fallback.GetDefaultPolicyAsync();\n",[59,1316,1317],{"class":61,"line":373},[59,1318,1319],{},"    public Task\u003CAuthorizationPolicy?> GetFallbackPolicyAsync() => _fallback.GetFallbackPolicyAsync();\n",[59,1321,1322],{"class":61,"line":379},[59,1323,288],{},[59,1325,1326],{"class":61,"line":385},[59,1327,90],{"emptyLinePlaceholder":89},[59,1329,1330],{"class":61,"line":391},[59,1331,1332],{},"builder.Services.AddSingleton\u003CIAuthorizationPolicyProvider, MinimumAgePolicyProvider>();\n",[15,1334,1335,1336,1339,1340,1342],{},"Always delegate unknown policy names to ",[24,1337,1338],{},"DefaultAuthorizationPolicyProvider"," so named policies\nregistered in ",[24,1341,170],{}," still work.",[10,1344,1346],{"id":1345},"authorization-in-minimal-apis","Authorization in minimal APIs",[50,1348,1350],{"className":52,"code":1349,"language":54,"meta":55,"style":55},"\u002F\u002F Per endpoint:\napp.MapGet(\"\u002Fpremium\", () => \"content\").RequireAuthorization(\"PremiumUser\");\n\n\u002F\u002F Group:\nvar api = app.MapGroup(\"\u002Fapi\").RequireAuthorization();\napi.MapGet(\"\u002Forders\",  (IOrderService svc) => svc.GetAll());\napi.MapGet(\"\u002Fhealth\",  () => Results.Ok()).AllowAnonymous();\n\n\u002F\u002F Inject ClaimsPrincipal directly:\napp.MapDelete(\"\u002Forders\u002F{id}\", async (int id, ClaimsPrincipal user, IOrderService svc) =>\n{\n    var authResult = await authzService.AuthorizeAsync(user, await svc.GetAsync(id), DocumentOperations.Delete);\n    if (!authResult.Succeeded) return Results.Forbid();\n    await svc.DeleteAsync(id);\n    return Results.NoContent();\n}).RequireAuthorization();\n",[24,1351,1352,1357,1362,1366,1371,1376,1381,1386,1390,1395,1400,1404,1409,1414,1419,1424],{"__ignoreMap":55},[59,1353,1354],{"class":61,"line":62},[59,1355,1356],{},"\u002F\u002F Per endpoint:\n",[59,1358,1359],{"class":61,"line":68},[59,1360,1361],{},"app.MapGet(\"\u002Fpremium\", () => \"content\").RequireAuthorization(\"PremiumUser\");\n",[59,1363,1364],{"class":61,"line":74},[59,1365,90],{"emptyLinePlaceholder":89},[59,1367,1368],{"class":61,"line":80},[59,1369,1370],{},"\u002F\u002F Group:\n",[59,1372,1373],{"class":61,"line":86},[59,1374,1375],{},"var api = app.MapGroup(\"\u002Fapi\").RequireAuthorization();\n",[59,1377,1378],{"class":61,"line":93},[59,1379,1380],{},"api.MapGet(\"\u002Forders\",  (IOrderService svc) => svc.GetAll());\n",[59,1382,1383],{"class":61,"line":99},[59,1384,1385],{},"api.MapGet(\"\u002Fhealth\",  () => Results.Ok()).AllowAnonymous();\n",[59,1387,1388],{"class":61,"line":105},[59,1389,90],{"emptyLinePlaceholder":89},[59,1391,1392],{"class":61,"line":111},[59,1393,1394],{},"\u002F\u002F Inject ClaimsPrincipal directly:\n",[59,1396,1397],{"class":61,"line":117},[59,1398,1399],{},"app.MapDelete(\"\u002Forders\u002F{id}\", async (int id, ClaimsPrincipal user, IOrderService svc) =>\n",[59,1401,1402],{"class":61,"line":123},[59,1403,273],{},[59,1405,1406],{"class":61,"line":129},[59,1407,1408],{},"    var authResult = await authzService.AuthorizeAsync(user, await svc.GetAsync(id), DocumentOperations.Delete);\n",[59,1410,1411],{"class":61,"line":135},[59,1412,1413],{},"    if (!authResult.Succeeded) return Results.Forbid();\n",[59,1415,1416],{"class":61,"line":140},[59,1417,1418],{},"    await svc.DeleteAsync(id);\n",[59,1420,1421],{"class":61,"line":146},[59,1422,1423],{},"    return Results.NoContent();\n",[59,1425,1426],{"class":61,"line":334},[59,1427,1428],{},"}).RequireAuthorization();\n",[10,1430,1432],{"id":1431},"recap","Recap",[15,1434,1435,1436,1439,1440,1443,1444,1446,1447,1449],{},"ASP.NET Core authorization separates ",[19,1437,1438],{},"what's required"," (requirements) from ",[19,1441,1442],{},"how to check it","\n(handlers). Named policies are the composable unit — define once, apply anywhere. Role-based checks\nare a convenient shortcut for simple cases. Resource-based authorization uses ",[24,1445,487],{},"\nfor decisions that depend on the data being accessed. The fallback policy pattern makes the entire\napp secure by default. Dynamic policies from ",[24,1448,1204],{}," eliminate boilerplate\nwhen the policy space is parameterized.",[1451,1452,1453],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":55,"searchDepth":68,"depth":68,"links":1455},[1456,1457,1458,1459,1460,1461,1463,1464,1465,1466,1467,1468,1469],{"id":12,"depth":68,"text":13},{"id":30,"depth":68,"text":31},{"id":159,"depth":68,"text":160},{"id":241,"depth":68,"text":242},{"id":469,"depth":68,"text":470},{"id":725,"depth":68,"text":1462},"The Authorize attribute in depth",{"id":879,"depth":68,"text":880},{"id":964,"depth":68,"text":965},{"id":1043,"depth":68,"text":1044},{"id":1082,"depth":68,"text":1083},{"id":1189,"depth":68,"text":1190},{"id":1345,"depth":68,"text":1346},{"id":1431,"depth":68,"text":1432},"How policy-based authorization works in ASP.NET Core — requirements, handlers, resource-based checks, and building dynamic policies when static attributes are not flexible enough.","medium","md",".NET Core","dotnet",{},"\u002Fblog\u002Fdotnet-authorization","\u002Fdotnet\u002Fsecurity\u002Fauthorization",{"title":5,"description":1470},"blog\u002Fdotnet-authorization","Authorization","Security","security","2026-06-23","PyI5Ya9v8cI8EPSQZSVcCoVHxVz_B0CJ95VUW9Ko_2U",1782244085901]