[{"data":1,"prerenderedAt":106},["ShallowReactive",2],{"qa-\u002Fdotnet\u002Fsecurity\u002Fauthentication":3},{"page":4,"siblings":94,"blog":103},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":85,"related":86,"seo":87,"seoDescription":88,"stem":89,"subtopic":6,"topic":90,"topicSlug":91,"updated":92,"__hash__":93},"qa\u002Fdotnet\u002Fsecurity\u002Fauthentication.md","Authentication",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md",".NET Core","dotnet",{},true,1,"\u002Fdotnet\u002Fsecurity\u002Fauthentication",[23,28,32,36,40,44,49,53,57,61,65,69,73,77,81],{"id":24,"difficulty":25,"q":26,"a":27},"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":29,"difficulty":25,"q":30,"a":31},"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":33,"difficulty":25,"q":34,"a":35},"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":37,"difficulty":25,"q":38,"a":39},"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":41,"difficulty":14,"q":42,"a":43},"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":45,"difficulty":46,"q":47,"a":48},"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":50,"difficulty":25,"q":51,"a":52},"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":54,"difficulty":14,"q":55,"a":56},"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":58,"difficulty":14,"q":59,"a":60},"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":62,"difficulty":14,"q":63,"a":64},"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":66,"difficulty":14,"q":67,"a":68},"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":70,"difficulty":14,"q":71,"a":72},"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":74,"difficulty":25,"q":75,"a":76},"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":78,"difficulty":14,"q":79,"a":80},"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":82,"difficulty":14,"q":83,"a":84},"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":11},"ASP.NET Core authentication interview questions — cookie auth, ClaimsPrincipal, authentication schemes, custom handlers, and challenge vs forbid.","dotnet\u002Fsecurity\u002Fauthentication","Security","security","2026-06-23","XXr-SC2tCMdMceWstHHIi5Z2Ll3upBYIKYlziL0u7To",[95,96,99],{"subtopic":6,"path":21,"order":20},{"subtopic":97,"path":98,"order":12},"Authorization","\u002Fdotnet\u002Fsecurity\u002Fauthorization",{"subtopic":100,"path":101,"order":102},"JWT Tokens","\u002Fdotnet\u002Fsecurity\u002Fjwt-tokens",3,{"path":104,"title":105},"\u002Fblog\u002Fdotnet-authentication","Authentication in ASP.NET Core",1782244118892]