[{"data":1,"prerenderedAt":106},["ShallowReactive",2],{"qa-\u002Fdotnet\u002Fsecurity\u002Fauthorization":3},{"page":4,"siblings":93,"blog":103},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":12,"path":20,"questions":21,"questionsCount":84,"related":85,"seo":86,"seoDescription":87,"stem":88,"subtopic":6,"topic":89,"topicSlug":90,"updated":91,"__hash__":92},"qa\u002Fdotnet\u002Fsecurity\u002Fauthorization.md","Authorization",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md",".NET Core","dotnet",{},true,"\u002Fdotnet\u002Fsecurity\u002Fauthorization",[22,27,31,35,40,44,48,52,56,60,64,68,72,76,80],{"id":23,"difficulty":24,"q":25,"a":26},"authz-policy-vs-role","easy","What is policy-based authorization and how does it differ from role-based authorization?","**Role-based authorization** checks whether the user belongs to a named role — it's\nsimple but inflexible. **Policy-based authorization** evaluates one or more composable\n*requirements*, enabling richer rules (minimum age, department, subscription tier, etc.).\n\n```csharp\n\u002F\u002F Role-based — check a single ClaimTypes.Role claim:\n[Authorize(Roles = \"Admin\")]           \u002F\u002F single role\n[Authorize(Roles = \"Admin,Manager\")]   \u002F\u002F either role (OR logic)\npublic class AdminController : ControllerBase { }\n\n\u002F\u002F Stacking [Authorize] means AND — both must pass:\n[Authorize(Roles = \"Admin\")]\n[Authorize(Roles = \"Manager\")]\npublic class AdminManagerOnlyController : ControllerBase { }\n\n\u002F\u002F Policy-based — named policy with composable requirements:\nbuilder.Services.AddAuthorizationBuilder()\n    .AddPolicy(\"SeniorEmployee\", policy =>\n        policy.RequireAuthenticatedUser()\n              .RequireRole(\"Employee\")\n              .RequireClaim(\"department\", \"Engineering\", \"Product\")\n              .RequireClaim(\"years_tenure\")\n              .AddRequirements(new MinimumTenureRequirement(years: 3)));\n\n[Authorize(Policy = \"SeniorEmployee\")]\npublic class SeniorPortalController : ControllerBase { }\n\n\u002F\u002F Policy centralizes rules — change the policy definition, all usages update:\n\u002F\u002F Role check is just a shortcut policy under the hood.\n```\n\n**Rule of thumb:** Use `Roles` for simple, coarse-grained access. Use policies for\nanything beyond a single role check — they're reusable, testable, and centralized.\n",{"id":28,"difficulty":24,"q":29,"a":30},"add-authorization","How do you configure authorization policies in ASP.NET Core?","Policies are registered in `AddAuthorization` (or `AddAuthorizationBuilder` .NET 8+)\nand applied with `[Authorize(Policy = \"...\")]` or `RequireAuthorization(\"...\")`.\n\n```csharp\n\u002F\u002F .NET 8+ fluent builder — cleaner API:\nbuilder.Services.AddAuthorizationBuilder()\n    .AddPolicy(\"AtLeast18\", policy =>\n        policy.Requirements.Add(new MinimumAgeRequirement(18)))\n\n    .AddPolicy(\"PremiumUser\", policy =>\n        policy.RequireAuthenticatedUser()\n              .RequireClaim(\"subscription\", \"premium\", \"enterprise\"))\n\n    .AddPolicy(\"AdminOnly\", policy =>\n        policy.RequireRole(\"Admin\"))\n\n    .SetDefaultPolicy(new AuthorizationPolicyBuilder()\n        .RequireAuthenticatedUser()\n        .Build())\n\n    .SetFallbackPolicy(null); \u002F\u002F null = allow anonymous by default (change if needed)\n\n\u002F\u002F .NET 6\u002F7 style:\nbuilder.Services.AddAuthorization(options =>\n{\n    options.AddPolicy(\"AtLeast18\", policy =>\n        policy.Requirements.Add(new MinimumAgeRequirement(18)));\n});\n\n\u002F\u002F Apply in controllers:\n[Authorize(Policy = \"PremiumUser\")]\npublic class PremiumController : ControllerBase { }\n\n\u002F\u002F Apply in minimal APIs:\napp.MapGet(\"\u002Fpremium\", () => \"premium content\")\n   .RequireAuthorization(\"PremiumUser\");\n\n\u002F\u002F Combine policies — both must pass (AND logic):\napp.MapPost(\"\u002Fadmin\u002Fpremium\", () => \"admin + premium\")\n   .RequireAuthorization(\"AdminOnly\", \"PremiumUser\");\n```\n\n**Rule of thumb:** Define policies centrally during startup — avoid scattering role\nstrings across controllers. Centralizing rules means a single change updates every\nendpoint that uses that policy.\n",{"id":32,"difficulty":14,"q":33,"a":34},"requirements-handlers","What are authorization requirements and handlers, and how do they work together?","An **`IAuthorizationRequirement`** is a data object that describes what must be true.\nAn **`IAuthorizationHandler`** evaluates whether the requirement is met for a given\nuser and resource.\n\n```csharp\n\u002F\u002F 1. Define the requirement (plain data, no logic):\npublic class MinimumAgeRequirement : IAuthorizationRequirement\n{\n    public int MinimumAge { get; }\n    public MinimumAgeRequirement(int minimumAge) => MinimumAge = minimumAge;\n}\n\n\u002F\u002F 2. Implement the handler (all logic lives 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        {\n            \u002F\u002F Don't call Fail() — let other handlers try if any are registered:\n            return Task.CompletedTask;\n        }\n\n        var age = DateTime.Today.Year - DateTime.Parse(dob).Year;\n\n        if (age >= requirement.MinimumAge)\n            context.Succeed(requirement); \u002F\u002F requirement satisfied — call this to pass\n        else\n            context.Fail();               \u002F\u002F explicitly deny (overrides any Succeed)\n\n        return Task.CompletedTask;\n    }\n}\n\n\u002F\u002F 3. Register both:\nbuilder.Services.AddSingleton\u003CIAuthorizationHandler, MinimumAgeHandler>();\n\nbuilder.Services.AddAuthorizationBuilder()\n    .AddPolicy(\"AtLeast18\", policy =>\n        policy.Requirements.Add(new MinimumAgeRequirement(18)));\n\n\u002F\u002F One requirement can have multiple handlers — any Succeed passes the requirement:\n\u002F\u002F All requirements in a policy must pass for the policy to succeed.\n```\n\n**Rule of thumb:** Keep requirements as pure data (no logic). All evaluation logic\nbelongs in the handler. This separation makes handlers independently testable with\njust a constructed `AuthorizationHandlerContext`.\n",{"id":36,"difficulty":37,"q":38,"a":39},"resource-based-authz","hard","What is resource-based authorization and when do you need it?","**Resource-based authorization** evaluates permissions against a specific resource\ninstance — \"can *this user* edit *this document*?\" — as opposed to global role checks.\nIt uses `IAuthorizationService` directly in code because the resource isn't known at\nroute decoration time.\n\n```csharp\n\u002F\u002F Requirement — carries the operation:\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 at runtime:\npublic class DocumentAuthorizationHandler\n    : AuthorizationHandler\u003COperationAuthorizationRequirement, Document>\n{\n    protected override Task HandleRequirementAsync(\n        AuthorizationHandlerContext context,\n        OperationAuthorizationRequirement requirement,\n        Document document)   \u002F\u002F ← the actual resource instance\n    {\n        var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);\n\n        if (requirement.Name == \"Read\" && document.IsPublic)\n            context.Succeed(requirement);\n        else if (document.OwnerId == userId)\n            context.Succeed(requirement);  \u002F\u002F owner can do anything\n        else if (requirement.Name != \"Delete\" && context.User.IsInRole(\"Editor\"))\n            context.Succeed(requirement);  \u002F\u002F editors can read\u002Fedit but not delete\n\n        return Task.CompletedTask;\n    }\n}\n\n\u002F\u002F Register:\nbuilder.Services.AddSingleton\u003CIAuthorizationHandler, DocumentAuthorizationHandler>();\n\n\u002F\u002F Call from a controller or service — 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(\n        User, document, DocumentOperations.Edit);\n\n    if (!authResult.Succeeded) return Forbid();\n\n    await _repo.UpdateAsync(document, dto);\n    return NoContent();\n}\n```\n\n**Rule of thumb:** Use `[Authorize(Policy = \"...\")]` for global checks (role, age).\nUse `IAuthorizationService.AuthorizeAsync(user, resource, requirement)` when the\ndecision depends on the data being accessed.\n",{"id":41,"difficulty":24,"q":42,"a":43},"authorize-attribute","How does the [Authorize] attribute work and what are its key parameters?","`[Authorize]` is an action filter that runs after authentication and before the action\nmethod. It evaluates the user's `ClaimsPrincipal` against the specified roles, policy,\nor authentication scheme.\n\n```csharp\n\u002F\u002F Bare [Authorize] — requires IsAuthenticated = true (default policy):\n[Authorize]\npublic IActionResult Profile() => Ok(User.Identity!.Name);\n\n\u002F\u002F Role — checks ClaimTypes.Role claim (comma = OR, stacked = AND):\n[Authorize(Roles = \"Admin,SuperAdmin\")]       \u002F\u002F Admin OR SuperAdmin\npublic IActionResult AdminArea() => Ok();\n\n[Authorize(Roles = \"Admin\")]\n[Authorize(Roles = \"Manager\")]               \u002F\u002F Admin AND Manager\npublic IActionResult AdminManager() => Ok();\n\n\u002F\u002F Named policy:\n[Authorize(Policy = \"PremiumUser\")]\npublic IActionResult PremiumContent() => Ok();\n\n\u002F\u002F Specific authentication scheme (evaluate with this scheme only):\n[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]\npublic IActionResult ApiEndpoint() => Ok();\n\n\u002F\u002F Controller-level + action-level (both must pass):\n[Authorize]                       \u002F\u002F all actions require auth\npublic class AccountController : ControllerBase\n{\n    [AllowAnonymous]              \u002F\u002F this action overrides — allows anonymous\n    public IActionResult Login() => Ok();\n\n    [Authorize(Roles = \"Admin\")] \u002F\u002F this action requires auth + Admin role\n    public IActionResult AdminAction() => Ok();\n\n    public IActionResult Profile() => Ok(); \u002F\u002F uses controller-level [Authorize]\n}\n```\n\n**Rule of thumb:** Prefer `[Authorize]` at the controller level with `[AllowAnonymous]`\non exceptions — it's harder to accidentally expose an endpoint. Avoid sprinkling\n`[AllowAnonymous]` at the controller level with `[Authorize]` on individual actions.\n",{"id":45,"difficulty":14,"q":46,"a":47},"iauthorizationservice","How do you use IAuthorizationService for imperative authorization checks?","**`IAuthorizationService`** allows runtime authorization checks in service\u002Fcontroller\ncode rather than declarative attribute-only checks. Inject it and call\n`AuthorizeAsync`.\n\n```csharp\n\u002F\u002F Controller — inject IAuthorizationService:\n[ApiController]\n[Route(\"api\u002Fdocuments\")]\npublic class DocumentsController : ControllerBase\n{\n    private readonly IAuthorizationService _authz;\n    private readonly IDocumentRepository _repo;\n\n    public DocumentsController(IAuthorizationService authz, IDocumentRepository repo)\n    {\n        _authz = authz;\n        _repo  = repo;\n    }\n\n    [HttpGet(\"{id}\")]\n    public async Task\u003CIActionResult> Get(int id)\n    {\n        var doc = await _repo.GetAsync(id);\n        if (doc is null) return NotFound();\n\n        \u002F\u002F Imperative check — policy name:\n        var result = await _authz.AuthorizeAsync(User, \"CanReadDocuments\");\n        if (!result.Succeeded) return Forbid();\n\n        return Ok(doc);\n    }\n\n    [HttpDelete(\"{id}\")]\n    public async Task\u003CIActionResult> Delete(int id)\n    {\n        var doc = await _repo.GetAsync(id);\n        if (doc is null) return NotFound();\n\n        \u002F\u002F Imperative check — resource-based with specific requirement:\n        var result = await _authz.AuthorizeAsync(User, doc, DocumentOperations.Delete);\n        if (!result.Succeeded) return Forbid();\n\n        await _repo.DeleteAsync(id);\n        return NoContent();\n    }\n}\n\n\u002F\u002F In a service (not a controller) — the user must be passed in:\npublic class DocumentService\n{\n    private readonly IAuthorizationService _authz;\n\n    public async Task\u003Cbool> CanDeleteAsync(ClaimsPrincipal user, Document doc)\n    {\n        var result = await _authz.AuthorizeAsync(user, doc, DocumentOperations.Delete);\n        return result.Succeeded;\n    }\n}\n```\n\n**Rule of thumb:** Use `IAuthorizationService` when the authorization decision depends\non runtime data (the resource itself). Use `[Authorize(Policy = \"...\")]` for static\nchecks that don't need a resource instance.\n",{"id":49,"difficulty":14,"q":50,"a":51},"claims-based-authz","How do you authorize based on specific claim values rather than roles?","`RequireClaim` in a policy checks for the presence of a claim type, or optionally\nvalidates that its value is in an allowed set.\n\n```csharp\nbuilder.Services.AddAuthorizationBuilder()\n\n    \u002F\u002F Require the claim to exist (any value):\n    .AddPolicy(\"VerifiedEmail\", policy =>\n        policy.RequireClaim(ClaimTypes.Email))\n\n    \u002F\u002F Require specific values (OR — any value in the set passes):\n    .AddPolicy(\"EURegion\", policy =>\n        policy.RequireClaim(\"region\", \"EU\", \"EEA\", \"CH\"))\n\n    \u002F\u002F Combine multiple claim requirements (AND):\n    .AddPolicy(\"VerifiedEUUser\", policy =>\n        policy.RequireAuthenticatedUser()\n              .RequireClaim(ClaimTypes.Email)\n              .RequireClaim(\"email_verified\", \"true\")\n              .RequireClaim(\"region\", \"EU\", \"EEA\"))\n\n    \u002F\u002F Custom requirement for complex value logic:\n    .AddPolicy(\"PremiumOrTrial\", policy =>\n        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\n\u002F\u002F RequireAssertion — inline lambda for simple one-off logic:\nbuilder.Services.AddAuthorizationBuilder()\n    .AddPolicy(\"WeekdayOnly\", policy =>\n        policy.RequireAssertion(_ =>\n            DateTime.UtcNow.DayOfWeek is not DayOfWeek.Saturday\n                                       and not DayOfWeek.Sunday));\n```\n\n**Rule of thumb:** Use `RequireClaim` for attribute-style checks that map neatly to a\nclaim value. Use `RequireAssertion` for inline logic that doesn't warrant a full\nrequirement+handler pair. Move complex, reusable logic to a proper handler.\n",{"id":53,"difficulty":14,"q":54,"a":55},"fallback-default-policy","What are the default policy and fallback policy in ASP.NET Core authorization?","The **default policy** is applied when `[Authorize]` is used with no policy name.\nThe **fallback policy** is applied to every endpoint that has *no* authorization\nmetadata at all — including endpoints that never have `[Authorize]` on them.\n\n```csharp\nbuilder.Services.AddAuthorizationBuilder()\n    \u002F\u002F Applied when [Authorize] has no parameters:\n    .SetDefaultPolicy(new AuthorizationPolicyBuilder()\n        .RequireAuthenticatedUser()\n        .Build())\n\n    \u002F\u002F Applied to ALL endpoints with no [Authorize] attribute\n    \u002F\u002F (opt-in secure-by-default):\n    .SetFallbackPolicy(new AuthorizationPolicyBuilder()\n        .RequireAuthenticatedUser()\n        .Build());\n\u002F\u002F With a fallback policy, every route is protected unless explicitly opened:\n\u002F\u002F app.MapGet(\"\u002Fpublic\", ...).AllowAnonymous();\n\n\u002F\u002F Secure-by-default pattern with minimal API groups:\nvar secured = app.MapGroup(\"\u002Fapi\").RequireAuthorization();    \u002F\u002F all children protected\nvar open    = app.MapGroup(\"\u002Fpublic\").AllowAnonymous();        \u002F\u002F all children public\n\n\u002F\u002F Disable fallback for a specific endpoint:\nsecured.MapGet(\"\u002Fhealth\", () => \"ok\").AllowAnonymous();\n\n\u002F\u002F Without a fallback policy (default behavior) — anonymous access is allowed\n\u002F\u002F to any route without [Authorize]. Easy to accidentally expose sensitive routes.\n```\n\n**Rule of thumb:** Set a fallback policy of `RequireAuthenticatedUser` in API-only\napplications — it makes the secure path the default and forces you to explicitly\nopt out with `[AllowAnonymous]`. This prevents accidentally exposing endpoints.\n",{"id":57,"difficulty":24,"q":58,"a":59},"allow-anonymous","How does [AllowAnonymous] work and where should you use it?","`[AllowAnonymous]` bypasses all authorization requirements for the endpoint it decorates,\nincluding the fallback policy. It's absolute — no policy can override it.\n\n```csharp\n\u002F\u002F [AllowAnonymous] at action level overrides [Authorize] at controller level:\n[Authorize]\npublic class AccountController : ControllerBase\n{\n    [AllowAnonymous]        \u002F\u002F no auth needed — this action is public\n    [HttpGet(\"login\")]\n    public IActionResult Login() => View();\n\n    [AllowAnonymous]        \u002F\u002F public — return form to browser\n    [HttpPost(\"login\")]\n    public async Task\u003CIActionResult> Login([FromBody] LoginDto dto) { \u002F* ... *\u002F return Ok(); }\n\n    [HttpGet(\"profile\")]    \u002F\u002F inherits [Authorize] from controller — requires auth\n    public IActionResult Profile() => Ok(User.Identity!.Name);\n\n    [Authorize(Roles = \"Admin\")]  \u002F\u002F requires auth AND Admin role\n    [HttpGet(\"admin\")]\n    public IActionResult Admin() => Ok();\n}\n\n\u002F\u002F In minimal APIs:\nvar api = app.MapGroup(\"\u002Fapi\").RequireAuthorization();\napi.MapGet(\"\u002Fproducts\", (IProductService svc) => svc.GetAll()); \u002F\u002F protected\napi.MapGet(\"\u002Fhealth\",   () => Results.Ok()).AllowAnonymous();    \u002F\u002F public\n\n\u002F\u002F [AllowAnonymous] defeats even a fallback policy:\n\u002F\u002F If SetFallbackPolicy(requireAuth), AllowAnonymous still bypasses it.\n```\n\n**Rule of thumb:** Put `[Authorize]` at the highest scope (controller, group) and\n`[AllowAnonymous]` at the lowest scope (individual actions\u002Fendpoints). Never put\n`[AllowAnonymous]` at the controller level — it becomes invisible to callers adding\nnew protected actions.\n",{"id":61,"difficulty":14,"q":62,"a":63},"global-authorize-filter","How do you add a global authorization filter so that every endpoint requires authentication?","Add `AuthorizeFilter` globally in MVC options, or set a fallback policy. Both\napproaches require authentication on every route unless `[AllowAnonymous]` is present.\n\n```csharp\n\u002F\u002F Option 1 — global MVC filter (controllers only, not minimal APIs):\nbuilder.Services.AddControllers(options =>\n{\n    var policy = new AuthorizationPolicyBuilder()\n        .RequireAuthenticatedUser()\n        .Build();\n    options.Filters.Add(new AuthorizeFilter(policy));\n});\n\n\u002F\u002F Option 2 — fallback policy (works for controllers + minimal APIs):\nbuilder.Services.AddAuthorizationBuilder()\n    .SetFallbackPolicy(new AuthorizationPolicyBuilder()\n        .RequireAuthenticatedUser()\n        .Build());\n\n\u002F\u002F Option 3 — MapGroup with RequireAuthorization (minimal API scoping):\nvar api = app.MapGroup(\"\u002Fapi\").RequireAuthorization();\n\n\u002F\u002F In all three cases, open specific endpoints with [AllowAnonymous]:\n[AllowAnonymous]\n[HttpGet(\"public-status\")]\npublic IActionResult PublicStatus() => Ok(\"online\");\n\n\u002F\u002F Recommendation: prefer the fallback policy over the MVC filter\n\u002F\u002F because it covers both controllers and minimal API endpoints.\n```\n\n**Rule of thumb:** For API-only applications, set the fallback policy to\n`RequireAuthenticatedUser` — it's the single secure-by-default knob that covers\nevery routing mechanism.\n",{"id":65,"difficulty":37,"q":66,"a":67},"policy-provider","What is IAuthorizationPolicyProvider and when would you implement a custom one?","**`IAuthorizationPolicyProvider`** is called by the authorization system to look up\npolicies by name. The default implementation searches the policies registered in\n`AddAuthorization`. A custom provider lets you generate policies dynamically — useful\nfor parameterized policies like `MinimumAge:18` without registering every variant\nupfront.\n\n```csharp\n\u002F\u002F Without a custom provider, every variant must be registered explicitly:\n\u002F\u002F .AddPolicy(\"MinimumAge:16\", ...) .AddPolicy(\"MinimumAge:18\", ...) — tedious\n\n\u002F\u002F With a custom provider — generate policies on demand:\npublic class MinimumAgePolicyProvider : IAuthorizationPolicyProvider\n{\n    private const string PolicyPrefix = \"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(PolicyPrefix, StringComparison.OrdinalIgnoreCase))\n        {\n            if (int.TryParse(policyName[PolicyPrefix.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        }\n        return _fallback.GetPolicyAsync(policyName); \u002F\u002F delegate to default\n    }\n\n    public Task\u003CAuthorizationPolicy> GetDefaultPolicyAsync()\n        => _fallback.GetDefaultPolicyAsync();\n\n    public Task\u003CAuthorizationPolicy?> GetFallbackPolicyAsync()\n        => _fallback.GetFallbackPolicyAsync();\n}\n\n\u002F\u002F Register (replaces the default provider):\nbuilder.Services.AddSingleton\u003CIAuthorizationPolicyProvider, MinimumAgePolicyProvider>();\n\n\u002F\u002F Use parameterized policy names anywhere:\n[Authorize(Policy = \"MinimumAge:21\")]\npublic IActionResult AlcoholSection() => Ok();\n\n[Authorize(Policy = \"MinimumAge:16\")]\npublic IActionResult TeenSection() => Ok();\n```\n\n**Rule of thumb:** Implement `IAuthorizationPolicyProvider` only when you have a family\nof policies that differ only by a parameter (age, permission ID, etc.). Always fall back\nto `DefaultAuthorizationPolicyProvider` for all other policy names.\n",{"id":69,"difficulty":14,"q":70,"a":71},"authorization-middleware-order","Why does middleware order matter for authorization, and what breaks if it is wrong?","Authorization middleware relies on `HttpContext.User` being populated before it runs.\nIf `UseAuthorization` is placed before `UseAuthentication`, every request arrives with\nan anonymous principal and all `[Authorize]` checks fail or pass incorrectly.\n\n```csharp\n\u002F\u002F Correct order — build, authenticate, then authorize:\nvar app = builder.Build();\n\napp.UseRouting();          \u002F\u002F 1. resolve the endpoint (needed by auth middleware)\napp.UseAuthentication();   \u002F\u002F 2. populate HttpContext.User from cookie or token\napp.UseAuthorization();    \u002F\u002F 3. evaluate [Authorize] against the populated User\napp.MapControllers();      \u002F\u002F 4. dispatch to the controller action\n\n\u002F\u002F Wrong — authorization runs before the user identity is established:\n\u002F\u002F app.UseAuthorization();  \u002F\u002F HttpContext.User = anonymous ClaimsPrincipal\n\u002F\u002F app.UseAuthentication(); \u002F\u002F too late — authorization already ran\n\n\u002F\u002F Wrong — routing not called before auth in older templates:\n\u002F\u002F app.UseAuthentication();\n\u002F\u002F app.UseAuthorization();\n\u002F\u002F app.UseRouting(); \u002F\u002F endpoint metadata (like [Authorize]) not yet resolved\n\n\u002F\u002F Effect of wrong order:\n\u002F\u002F [Authorize] endpoints return 401 for every request, including authenticated users.\n\u002F\u002F Resource-based checks via IAuthorizationService still work (called in action bodies)\n\u002F\u002F but attribute-based checks at the middleware level silently fail.\n\n\u002F\u002F Note: UseEndpoints (pre-.NET 6) must also come after UseRouting:\n\u002F\u002F app.UseRouting();\n\u002F\u002F app.UseAuthentication();\n\u002F\u002F app.UseAuthorization();\n\u002F\u002F app.UseEndpoints(e => e.MapControllers());\n```\n\n**Rule of thumb:** The canonical order is `UseRouting → UseAuthentication → UseAuthorization\n→ MapControllers\u002FMapGet`. In .NET 6+ minimal hosting this is the default, but always\nverify when customizing the pipeline.\n",{"id":73,"difficulty":37,"q":74,"a":75},"permission-based-authz","How do you implement fine-grained permission-based authorization beyond roles?","Roles are coarse-grained. For fine-grained control, model **permissions** as claims\nor a custom requirement, and load them from your data store during authentication.\n\n```csharp\n\u002F\u002F Option 1 — permission claims added at login time:\n\u002F\u002F Permissions are stored in the DB (per-user or per-role) and embedded in the token.\n\npublic class PermissionService\n{\n    private readonly AppDbContext _db;\n    public PermissionService(AppDbContext db) => _db = db;\n\n    public async Task\u003CIEnumerable\u003Cstring>> GetForUserAsync(string userId)\n        => await _db.UserPermissions\n            .Where(p => p.UserId == userId)\n            .Select(p => p.Name) \u002F\u002F e.g. \"orders:read\", \"orders:write\"\n            .ToListAsync();\n}\n\n\u002F\u002F Embed permissions as claims when the cookie\u002Ftoken is created:\nvar permissions = await _permissionService.GetForUserAsync(user.Id);\nvar claims = permissions.Select(p => new Claim(\"permission\", p)).ToList();\nclaims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id));\n\n\u002F\u002F Option 2 — custom requirement + handler that checks the claim:\npublic class PermissionRequirement : IAuthorizationRequirement\n{\n    public string Permission { get; }\n    public PermissionRequirement(string permission) => Permission = permission;\n}\n\npublic class PermissionHandler : AuthorizationHandler\u003CPermissionRequirement>\n{\n    protected override Task HandleRequirementAsync(\n        AuthorizationHandlerContext context,\n        PermissionRequirement requirement)\n    {\n        var hasPermission = context.User\n            .FindAll(\"permission\")\n            .Any(c => c.Value == requirement.Permission);\n\n        if (hasPermission)\n            context.Succeed(requirement);\n\n        return Task.CompletedTask;\n    }\n}\n\n\u002F\u002F Register and define policies:\nbuilder.Services.AddSingleton\u003CIAuthorizationHandler, PermissionHandler>();\n\nbuilder.Services.AddAuthorizationBuilder()\n    .AddPolicy(\"orders:read\",  p => p.AddRequirements(new PermissionRequirement(\"orders:read\")))\n    .AddPolicy(\"orders:write\", p => p.AddRequirements(new PermissionRequirement(\"orders:write\")));\n\n\u002F\u002F Use on endpoints:\n[Authorize(Policy = \"orders:write\")]\n[HttpPost(\"orders\")]\npublic IActionResult CreateOrder([FromBody] CreateOrderDto dto) => Ok();\n```\n\n**Rule of thumb:** Keep permissions granular and name them as `resource:action` strings\n(e.g., `orders:write`). Store them per-role in the DB so you can update permissions\nwithout redeploying; embed them in the token at login to avoid a DB lookup on every request.\n",{"id":77,"difficulty":37,"q":78,"a":79},"conditional-access","How do you implement conditional access rules, such as restricting endpoints by IP range or time of day?","Use `RequireAssertion` for simple inline rules, or implement a full\n`IAuthorizationRequirement` + handler for complex, reusable conditions. The\n`AuthorizationHandlerContext` gives you access to `HttpContext` via the resource.\n\n```csharp\n\u002F\u002F Simple time-of-day gate via RequireAssertion:\nbuilder.Services.AddAuthorizationBuilder()\n    .AddPolicy(\"BusinessHoursOnly\", policy =>\n        policy.RequireAuthenticatedUser()\n              .RequireAssertion(_ =>\n              {\n                  var now = DateTime.UtcNow;\n                  \u002F\u002F Allow Mon–Fri 09:00–17:00 UTC:\n                  return now.DayOfWeek is not DayOfWeek.Saturday\n                                        and not DayOfWeek.Sunday\n                      && now.Hour >= 9 && now.Hour \u003C 17;\n              }));\n\n\u002F\u002F IP range restriction — requirement + handler accessing HttpContext:\npublic class AllowedIpRequirement : IAuthorizationRequirement\n{\n    public IReadOnlyList\u003Cstring> AllowedPrefixes { get; }\n    public AllowedIpRequirement(params string[] prefixes) => AllowedPrefixes = prefixes;\n}\n\npublic class AllowedIpHandler : AuthorizationHandler\u003CAllowedIpRequirement>\n{\n    private readonly IHttpContextAccessor _http;\n    public AllowedIpHandler(IHttpContextAccessor http) => _http = http;\n\n    protected override Task HandleRequirementAsync(\n        AuthorizationHandlerContext context,\n        AllowedIpRequirement requirement)\n    {\n        var remoteIp = _http.HttpContext?.Connection.RemoteIpAddress?.ToString();\n        if (remoteIp is not null &&\n            requirement.AllowedPrefixes.Any(prefix => remoteIp.StartsWith(prefix)))\n        {\n            context.Succeed(requirement);\n        }\n        \u002F\u002F Note: not calling Fail() allows other handlers to decide if registered\n        return Task.CompletedTask;\n    }\n}\n\n\u002F\u002F Register and apply:\nbuilder.Services.AddHttpContextAccessor();\nbuilder.Services.AddSingleton\u003CIAuthorizationHandler, AllowedIpHandler>();\n\nbuilder.Services.AddAuthorizationBuilder()\n    .AddPolicy(\"InternalNetworkOnly\", policy =>\n        policy.RequireAuthenticatedUser()\n              .AddRequirements(new AllowedIpRequirement(\"10.0.\", \"192.168.\")));\n\n[Authorize(Policy = \"InternalNetworkOnly\")]\n[HttpGet(\"admin\u002Fdiagnostics\")]\npublic IActionResult Diagnostics() => Ok(\"internal only\");\n```\n\n**Rule of thumb:** Prefer `RequireAssertion` for stateless, self-contained conditions.\nUse a proper handler when the condition needs injected services (databases, HTTP context,\nconfiguration) — it keeps the logic testable and reusable across policies.\n",{"id":81,"difficulty":14,"q":82,"a":83},"authorization-result-handling","How do you return custom error responses when authorization fails, instead of the default 403?","Override `IAuthorizationMiddlewareResultHandler` (ASP.NET Core 5+) or handle failures\nin `ProblemDetails` middleware. For specific schemes, use the scheme's event callbacks.\n\n```csharp\n\u002F\u002F Option 1 — custom IAuthorizationMiddlewareResultHandler:\npublic class CustomAuthorizationResultHandler : IAuthorizationMiddlewareResultHandler\n{\n    private readonly AuthorizationMiddlewareResultHandler _default = new();\n\n    public async Task HandleAsync(\n        RequestDelegate next,\n        HttpContext context,\n        AuthorizationPolicy policy,\n        PolicyAuthorizationResult authorizeResult)\n    {\n        if (authorizeResult.Forbidden && authorizeResult.AuthorizationFailure is not null)\n        {\n            \u002F\u002F Return structured JSON instead of plain 403:\n            context.Response.StatusCode  = StatusCodes.Status403Forbidden;\n            context.Response.ContentType = \"application\u002Fproblem+json\";\n            await context.Response.WriteAsJsonAsync(new\n            {\n                type   = \"https:\u002F\u002Ftools.ietf.org\u002Fhtml\u002Frfc7231#section-6.5.3\",\n                title  = \"Forbidden\",\n                status = 403,\n                detail = \"You do not have permission to perform this action.\",\n                failedRequirements = authorizeResult.AuthorizationFailure\n                    .FailedRequirements\n                    .Select(r => r.GetType().Name),\n            });\n            return;\n        }\n\n        await _default.HandleAsync(next, context, policy, authorizeResult);\n    }\n}\n\n\u002F\u002F Register (replaces the default handler):\nbuilder.Services.AddSingleton\u003CIAuthorizationMiddlewareResultHandler,\n    CustomAuthorizationResultHandler>();\n\n\u002F\u002F Option 2 — UseStatusCodePages for simple HTML\u002Ftext responses:\napp.UseStatusCodePages(async ctx =>\n{\n    if (ctx.HttpContext.Response.StatusCode == 403)\n    {\n        ctx.HttpContext.Response.ContentType = \"application\u002Fjson\";\n        await ctx.HttpContext.Response.WriteAsJsonAsync(new { error = \"Access denied\" });\n    }\n});\n\n\u002F\u002F Option 3 — for cookie auth: customize the redirect rather than status code:\n.AddCookie(options =>\n{\n    options.Events.OnRedirectToAccessDenied = ctx =>\n    {\n        ctx.Response.StatusCode  = 403;\n        ctx.Response.ContentType = \"application\u002Fjson\";\n        return ctx.Response.WriteAsJsonAsync(new { error = \"Forbidden\" });\n    };\n});\n```\n\n**Rule of thumb:** Implement `IAuthorizationMiddlewareResultHandler` for API projects\nthat need RFC 7807 Problem Details on every auth failure. For cookie-based web apps,\noverride the `OnRedirectToAccessDenied` event to avoid a redirect loop on API calls.\n",15,null,{"description":11},"ASP.NET Core authorization interview questions — policy-based authorization, custom requirements and handlers, resource-based auth, and dynamic policies.","dotnet\u002Fsecurity\u002Fauthorization","Security","security","2026-06-23","poPHLcUwjB4ywKfYyLuq9jnCR6iHgEEXN1QM0X4F8JA",[94,98,99],{"subtopic":95,"path":96,"order":97},"Authentication","\u002Fdotnet\u002Fsecurity\u002Fauthentication",1,{"subtopic":6,"path":20,"order":12},{"subtopic":100,"path":101,"order":102},"JWT Tokens","\u002Fdotnet\u002Fsecurity\u002Fjwt-tokens",3,{"path":104,"title":105},"\u002Fblog\u002Fdotnet-authorization","Policy-Based Authorization in ASP.NET Core",1782244119629]