Stolen session cookies still beat most exotic app bugs. On shared or VPS Windows hosts, your ASP.NET Core site sits behind IIS, TLS terminates at the edge, and auth cookies travel on every authenticated request. If those cookies are missing Secure/HttpOnly, use a weak SameSite policy, or live forever with a predictable name, an XSS or network slip becomes a full account takeover.

This is a production hardening pass for cookie authentication—not a rewrite of your identity stack. Configure CookiePolicy and CookieAuthenticationOptions once, verify them under HTTPS on IIS, and treat the cookie as a bearer token you refuse to leak.

For ASP.NET Core 10 cookie auth, set Secure, HttpOnly, and SameSite explicitly. Do not rely on framework defaults remaining “good enough” across upgrades or reverse-proxy layouts. Prefer SameSite=Lax for classic server-rendered apps that post back to your origin; use Strict when the app never needs cross-site top-level navigations to carry the session. Avoid None unless you truly run a cross-site embed and always pair None with Secure.

csharp
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.Cookie.Name = "__Host-appauth";
        options.Cookie.HttpOnly = true;
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
        options.Cookie.SameSite = SameSiteMode.Lax;
        options.Cookie.Path = "/";
        options.SlidingExpiration = true;
        options.ExpireTimeSpan = TimeSpan.FromHours(8);
        options.LoginPath = "/account/login";
        options.AccessDeniedPath = "/account/denied";
        options.Cookie.MaxAge = options.ExpireTimeSpan;
    });

builder.Services.Configure<CookiePolicyOptions>(options =>
{
    options.MinimumSameSitePolicy = SameSiteMode.Lax;
    options.Secure = CookieSecurePolicy.Always;
    options.HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.Always;
});

The __Host- prefix is deliberate: browsers require Secure, Path=/, and no Domain attribute. That stops subdomain cookie injection on multi-app hosts where you might otherwise set Domain=.example.com out of habit. If you must share auth across subdomains, drop the prefix and accept the wider blast radius—document that choice.

#IIS and HTTPS: make Secure mean something

CookieSecurePolicy.Always only works if the app believes the request is HTTPS. On IIS, TLS often terminates at the site binding or a front proxy. Forwarded headers must be correct or the middleware will see http:// and either refuse to issue the cookie or issue it inconsistently across environments.

csharp
// Program.cs — only after you trust the proxy/IIS arrangement
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    // Restrict known proxies/networks in production; do not leave open.
});

var app = builder.Build();
app.UseForwardedHeaders();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();

Confirm the site binding in IIS uses HTTPS and that HTTP redirects happen before auth cookies are set. After deploy, open DevTools → Application → Cookies and verify Secure, HttpOnly, SameSite, and the exact name. A cookie that appears on an http:// test hostname is a misconfiguration waiting for production traffic.

#Lifetimes, sliding renewal, and logout

Long-lived cookies are convenient and dangerous. Eight-hour sliding expiration fits many internal and SaaS apps: active users stay signed in, idle sessions die without a separate absolute calendar policy. For higher-risk admin surfaces, shorten ExpireTimeSpan and disable sliding expiration so the ticket cannot be stretched indefinitely.

  • Call SignOutAsync on logout and clear the cookie path that matches issuance (Path=/ for __Host-).
  • Do not store raw credentials or refresh secrets inside the cookie payload; keep claims minimal (user id, roles you actually authorize against).
  • Pair cookie auth with antiforgery on state-changing form posts; SameSite is not a full CSRF replacement for every browser edge case.
  • Keep Data Protection keys stable across app pool recycles and multi-instance IIS farms so tickets still unprotect after a recycle (key ring on a shared path or protected store).

If you rotate the cookie name during an incident (suspected theft or XSS), deploy the new name and force re-login. Old cookies simply stop authenticating once the server no longer issues or accepts that name.

Connection strings and API keys do not belong in cookies, localStorage, or client-visible config. On Windows hosting, keep secrets in environment variables, IIS-level settings you do not deploy in public source, or a protected secret store your app reads at startup. web.config can hold encrypted connection strings, but treat repo copies as untrusted and never commit production credentials.

xml
<!-- Example: auth-related app settings only; secrets stay out of source -->
<aspNetCore processPath="dotnet"
            arguments=".\MyApp.dll"
            stdoutLogEnabled="false"
            hostingModel="inprocess">
  <environmentVariables>
    <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" />
  </environmentVariables>
</aspNetCore>

Also reject the pattern of duplicating the auth cookie into a non-HttpOnly “JS convenience” cookie. That undoes HttpOnly in one line and turns any XSS into session theft.

#Quick verification checklist

  • Browser shows Secure + HttpOnly + expected SameSite on the auth cookie only over HTTPS.
  • Name uses __Host- when you do not need subdomain sharing; Path=/; no Domain attribute.
  • Logout clears the cookie; expired idle sessions cannot call authenticated APIs.
  • App pool recycle does not mass-sign-out users (stable data-protection key ring).
  • No second copy of the session id readable from document.cookie.

Practical takeaway: treat the auth cookie as production infrastructure. In CookieAuthenticationOptions set Name, HttpOnly, SecurePolicy=Always, SameSite, Path, and a finite ExpireTimeSpan; enable UseCookiePolicy with the same floor; fix forwarded proto on IIS so Secure is honest; keep secrets and key rings off the client. After the next Web Deploy, spend two minutes in DevTools confirming the flags—those clicks catch more real incidents than another abstract threat model.