{"id":83253,"date":"2019-02-11T15:07:04","date_gmt":"2019-02-11T15:07:04","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=83253"},"modified":"2026-04-15T18:10:41","modified_gmt":"2026-04-15T18:10:41","slug":"using-auth-cookies-in-asp-net-core","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/using-auth-cookies-in-asp-net-core\/","title":{"rendered":"ASP.NET Core Cookie Authentication: Setup, JWT Claims, and Revocation"},"content":{"rendered":"<h2>Executive Summary<\/h2>\n<p><strong>ASP.NET Core&#8217;s cookie authentication middleware handles the full authentication lifecycle for web applications: generating and issuing an encrypted auth cookie on login, validating it on every request, and clearing it on logout. This article covers the complete implementation: configuring cookie options (expiry, SameSite, SecurePolicy), the login flow with ClaimsIdentity, embedding a JWT in the claims for downstream API access, logout and cookie clearing, cookie revocation for server-side user access changes, and managing cookie size through a distributed session store. Assumes a working ASP.NET MVC skeleton.<\/strong><\/p>\n<p>Cookie-based authentication is the popular choice to secure customer facing web apps. For .NET programmers, ASP.NET Core has a good approach that is worth looking into. In this take, I will delve deep into the auth cookie using ASP.NET Core 2.1. Version 2.1 is the latest LTS version as of the time of this writing. So, feel free to follow along, I\u2019ll assume you\u2019re on Visual Studio or have enough C# chops to use a text editor. I will omit namespaces and using statements to keep code samples focused. If you get stuck, download the sample code found at the end.<\/p>\n<p>With ASP.NET 2.1, you can use cookie-based authentication out of the box. There is no need for additional NuGet packages. New projects include a metapackage that has everything, which is <em>Microsoft.AspNetCore.App<\/em>. To follow along, type <code>dotnet new mvc<\/code> in a CLI or do <em>File &gt; New Project<\/em> in Visual Studio.<\/p>\n<p>For those of you who come from classic .NET, you may be aware of the <em>OWIN<\/em> auth cookie. You will be happy to know those same skills transfer over to ASP.NET Core quite well. The two implementations remain somewhat similar. With ASP.NET Core, you still configure the auth cookie, set up middleware, and set identity claims.<\/p>\n<h2>Setup<\/h2>\n<p>To begin, I\u2019ll assume you know enough about the ASP.NET MVC framework to gut the scaffolding into a skeleton web app. You need a <em>HomeController<\/em> with an <code>Index<\/code>, <code>Login<\/code>, <code>Logout<\/code>, and <code>Revoke<\/code> action methods. <code>Login<\/code> will redirect to <code>Index<\/code> after it signs you in, so it doesn\u2019t need a view. I\u2019ll omit showing view sample code since views are not the focus here. If you get lost, be sure to download the entire demo to play with it.<\/p>\n<p>I\u2019ll use debug logs to show critical events inside the cookie authentication. Be sure to enable debug logs in <em>appsettings.json<\/em> and disable Microsoft and system logs.<\/p>\n<p>My log setup looks like this:<\/p>\n<pre class=\"lang:c# theme:vs2012\">\"LogLevel\": {\n  \"Default\": \"Debug\",\n  \"System\": \"Warning\",\n  \"Microsoft\": \"Warning\"\n}<\/pre>\n<p>Now you\u2019re ready to build a basic app with cookie authentication. I\u2019ll forgo HTML forms with a user name and password input fields. These front-end concerns only add clutter to what is more important which is the auth cookie. Starting with a skeleton app shows how effective it is to add an auth cookie from scratch. The app will sign you in automatically and land on an Index page with an auth cookie. Then, you can log out or revoke user access. I want you to pay attention to what happens to the auth cookie as I put authentication in place.<\/p>\n<h2>Cookie Options<\/h2>\n<p>Begin by configuring auth cookie options through middleware inside the <strong>Startup<\/strong> class. Cookie options tell the authentication middleware how the cookie behaves in the browser. There are many options, but I will only focus on those that affect cookie security the most.<\/p>\n<ul>\n<li><strong>HttpOnly<\/strong>: A flag that says the cookie is only available to servers. The browser only sends the cookie but cannot access it through JavaScript.<\/li>\n<li><strong>SecurePolicy<\/strong>: This limits the cookie to HTTPS. I recommend setting this to Always in prod. Leave it set to None in local.<\/li>\n<li><strong>SameSite<\/strong>: Indicates whether the browser can use the cookie with cross-site requests. For OAuth authentication, set this to Lax. I am setting this to Strict because the auth cookie is only for a single site. Setting this to None does not set a cookie header value.<\/li>\n<\/ul>\n<p>There are cookie options for both the auth cookie and a global cookie policy. Stay alert since the cookie policy can override auth cookie options and vice versa. Setting <code>HttpOnly<\/code> to false in the auth cookie will override cookie policy options. While setting <code>SameSite<\/code> in the cookie policy overrides auth cookie options. In my demo, I\u2019ll illustrate both scenarios, so it is crystal clear how this works.<\/p>\n<p>In the <code>Startup<\/code> class, find the <code>ConfigureServices<\/code> method and type:<\/p>\n<pre class=\"lang:c# theme:vs2012\">services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)\n  .AddCookie(options =&gt;\n  {\n    options.Cookie.HttpOnly = true;\n    options.Cookie.SecurePolicy = _environment.IsDevelopment()\n      ? CookieSecurePolicy.None : CookieSecurePolicy.Always;\n      options.Cookie.SameSite = SameSiteMode.Lax;\n  });<\/pre>\n<p>This creates the middleware service with the <code>AddAuthentication<\/code> and <code>AddCookie<\/code> methods. <code>AuthenticationScheme<\/code> is useful when there is more than one auth cookie. Many instances of\u00a0 the cookie authentication let you protect endpoints with many schemes. You supply any string value; the default is set to Cookies. Note that the <strong>options<\/strong> object is an instance of the <code>CookieAuthenticationOptions<\/code> class.<\/p>\n<p>The <code>SecurePolicy<\/code> is set through a ternary operator that comes from <strong>_<\/strong><code>environment<\/code>. This is a private property that gets set in the <code>Startup<\/code> constructor. Add <code>IHostingEnvironment<\/code> as a parameter and let dependency injection do the rest.<\/p>\n<p>In this same <code>ConfigureServices<\/code> method, add the global cookie policy through middleware:<\/p>\n<pre class=\"lang:c# theme:vs2012\">services.Configure&lt;CookiePolicyOptions&gt;(options =&gt;\n{\n  options.MinimumSameSitePolicy = SameSiteMode.Strict;\n  options.HttpOnly = HttpOnlyPolicy.None;\n  options.Secure = _environment.IsDevelopment()\n    ? CookieSecurePolicy.None : CookieSecurePolicy.Always;\n});<\/pre>\n<p>Take a good look at <code>SameSite<\/code> and <code>HttpOnly<\/code> settings for both cookie options. When I set the auth cookie, you will see this set to <code>HttpOnly<\/code> and <code>Strict<\/code>. This illustrates how both options override each other.<\/p>\n<p>Invoke this middleware inside the request pipeline in the <code>Configure<\/code> method:<\/p>\n<pre class=\"lang:c# theme:vs2012\">app.UseCookiePolicy();\napp.UseAuthentication();<\/pre>\n<p>The cookie policy middleware is order sensitive. This means it only affects components after invocation. By invoking the authentication middleware, you will get a <code>HttpContext.User<\/code> property. Be sure to call this <code>UseAuthentication<\/code> method before calling <code>UseMvc<\/code>.<\/p>\n<h2>Login<\/h2>\n<p>In the <code>HomeController<\/code> add an <code>AllowAnonymous<\/code> filter to the Login and Logout methods. There are only two action methods available without an auth cookie.<\/p>\n<p>Inside the <code>Startup<\/code> class, look for the <code>AddMvc<\/code> extension method and add a global auth filter:<\/p>\n<pre class=\"lang:c# theme:vs2012\">services.AddMvc(options =&gt; options.Filters.Add(new AuthorizeFilter()))<\/pre>\n<p>With the app secure, configure the cookie name and login \/ logout paths. Find where the rest of the <code>CookieAuthenticationOptions<\/code> are and do:<\/p>\n<pre class=\"lang:c# theme:vs2012\">options.Cookie.Name = \"SimpleTalk.AuthCookieAspNetCore\";\noptions.LoginPath = \"\/Home\/Login\";\noptions.LogoutPath = \"\/Home\/Logout\";<\/pre>\n<p>This will cause the app to redirect to the login endpoint to sign in. However, before you can take this for a spin, you\u2019ll need to create the auth cookie.<\/p>\n<p>Do this inside the <code>Login<\/code> action method in the <code>HomeController<\/code>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">var claims = new List&lt;Claim&gt;\n{\n  new Claim(ClaimTypes.Name, Guid.NewGuid().ToString())\n};\n\nvar claimsIdentity = new ClaimsIdentity(\n  claims, CookieAuthenticationDefaults.AuthenticationScheme);\nvar authProperties = new AuthenticationProperties();\n\nawait HttpContext.SignInAsync(\n  CookieAuthenticationDefaults.AuthenticationScheme,\n  new ClaimsPrincipal(claimsIdentity),\n  authProperties);<\/pre>\n<p><code>AuthenticationProperties<\/code> drive further auth cookie behavior in the browser. For example, the <code>IsPersistent<\/code> property persists the cookie across browser sessions. Be sure to get explicit user consent when you enable this property. <code>ExpiresUtc<\/code> sets an absolute expiration, be sure to enable <code>IsPersistent<\/code> and set it to true. The default values will give you a session cookie that goes away when you close the tab or browser window. I find the default values in this object enough for most use cases.<\/p>\n<p>To take this for a spin load up the browser by going to the home or Index page. Note it redirects to Login which redirects back to Index with an auth cookie.<\/p>\n<p>Once this loads it looks something like this. Be sure to take a good look at how the auth cookie is set:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"875\" height=\"663\" class=\"wp-image-83255\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/02\/word-image.png\" \/><\/p>\n<h2>JWT Identity Claim<\/h2>\n<p>Often, an auth cookie isn\u2019t enough to secure API endpoints or microservices. For the web app to call a service, it can use a JWT bearer token to authenticate. To make the access token accessible, place it inside the identity claims.<\/p>\n<p>In the Login action method within <code>HomeController<\/code>, expand the list of claims with a JWT:<\/p>\n<pre class=\"lang:c# theme:vs2012\">var userId = Guid.NewGuid().ToString();\nvar claims = new List&lt;Claim&gt;\n{\n  new Claim(ClaimTypes.Name, userId),\n  new Claim(\"access_token\", GetAccessToken(userId))\n};\n\nprivate static string GetAccessToken(string userId)\n{\n  const string issuer = \"localhost\";\n  const string audience = \"localhost\";\n\n  var identity = new ClaimsIdentity(new List&lt;Claim&gt;\n  {\n    new Claim(\"sub\", userId)\n  });\n\n  var bytes = Encoding.UTF8.GetBytes(userId);\n  var key = new SymmetricSecurityKey(bytes);\n  var signingCredentials = new SigningCredentials(\n    key, SecurityAlgorithms.HmacSha256);\n\n  var now = DateTime.UtcNow;\n  var handler = new JwtSecurityTokenHandler();\n\n  var token = handler.CreateJwtSecurityToken(\n    issuer, audience, identity,\n    now, now.Add(TimeSpan.FromHours(1)),\n    now, signingCredentials);\n\n  return handler.WriteToken(token);\n}<\/pre>\n<p>I must caution, don\u2019t ever do this is in production. Here, I use the user id as the signing key which is symmetric to keep it simple. In a prod environment use an asymmetric signing key with public and private keys. Client apps will then use a well-known configuration endpoint to validate the JWT.<\/p>\n<p>Placing the JWT in <code>ClaimsIdentity<\/code> makes it accessible through the <code>HttpContex.User<\/code> property. For this app, say you want to put the JWT in a debug log to show off this fancy access token.<\/p>\n<p>In the <code>Startup<\/code> class, create this middleware inside the <code>Configure<\/code> method:<\/p>\n<pre class=\"lang:c# theme:vs2012\">app.Use(async (context, next) =&gt;\n{\n  var principal = context.User as ClaimsPrincipal;\n  var accessToken = principal?.Claims\n    .FirstOrDefault(c =&gt; c.Type == \"access_token\");\n\n  if (accessToken != null)\n  {\n    _logger.LogDebug(accessToken.Value);\n  }\n\n  await next();\n});<\/pre>\n<p>The <strong>_<\/strong><code>logger<\/code> is another private property you set through the constructor. Add <code>ILogger&lt;Startup&gt;<\/code> as a parameter and let dependency injection do the rest. Note the <code>ClaimsPrincipal<\/code> has a list of <code>Claims<\/code> you can iterate through. What I find useful is to look for a <code>Type<\/code> of claim like an <code>access_token<\/code> and get a <code>Value<\/code>. Because this is middleware always call <code>next()<\/code> so it doesn\u2019t block the request pipeline.<\/p>\n<h2>Logout<\/h2>\n<p>To log out of the web app and clear the auth cookie do:<\/p>\n<pre class=\"lang:c# theme:vs2012\">await HttpContext.SignOutAsync(\n  CookieAuthenticationDefaults.AuthenticationScheme);<\/pre>\n<p>This belongs inside the Logout action method in the <code>HomeController<\/code>. Note that you specify the authentication scheme. This tells the sign-out method which auth cookie it needs to blot out. Inspecting HTTP response headers reveals <code>Cache-Control<\/code> and <code>Pragma<\/code> headers set to <code>no-cache<\/code>. This shows the auth cookie disables browser caching when it wants to update the cookie. The <code>Login<\/code> action method responds with the same HTTP headers.<\/p>\n<h2>Revocation<\/h2>\n<p>There are use cases where the app needs to react to back-end user access changes. The auth cookie will secure the application, but, remains valid for the lifetime of the cookie. With a valid cookie, the end-user will not see any changes until they log out or the cookie expires. In ASP.NET Core 2.1, one way to validate changes is through cookie authentication events. The validation event can do back-end lookups from identity claims in the auth cookie. Create the event by extending <code>CookieAuthenticationEvents<\/code>. Override the <code>ValidatePrincipal<\/code> method and set the event in the auth cookie options.<\/p>\n<p>For example:<\/p>\n<pre class=\"lang:c# theme:vs2012\">public class RevokeAuthenticationEvents : CookieAuthenticationEvents\n{\n  private readonly IMemoryCache _cache;\n  private readonly ILogger _logger;\n\n  public RevokeAuthenticationEvents(\n    IMemoryCache cache,\n    ILogger&lt;RevokeAuthenticationEvents&gt; logger)\n  {\n    _cache = cache;\n    _logger = logger;\n  }\n\n  public override Task ValidatePrincipal(\n    CookieValidatePrincipalContext context)\n  {\n    var userId = context.Principal.Claims\n      .First(c =&gt; c.Type == ClaimTypes.Name);\n\n    if (_cache.Get&lt;bool&gt;(\"revoke-\" + userId.Value))\n    {\n      context.RejectPrincipal();\n\n      _cache.Remove(\"revoke-\" + userId.Value);\n      _logger.LogDebug(\"Access has been revoked for: \"\n        + userId.Value + \".\");\n    }\n\n    return Task.CompletedTask;\n  }\n}<\/pre>\n<p>To have <code>IMemoryCache<\/code> set by dependency injection, put <code>AddMemoryCache<\/code> inside the <code>ConfigureSerices<\/code> method in the <code>Startup<\/code> class. Calling <code>RejectPrincipal<\/code> has an immediate effect and kicks you back out to Login to get a new auth cookie. Note this relies on in-memory persistence which gets set in the Revoke action method. Keep in mind that this event runs once per every request, so you want to use an efficient caching strategy. Doing an expensive lookup at every request will affect performance.<\/p>\n<p>Revoke access by setting the cache inside the <code>Revoke<\/code> method in the <code>HomeController<\/code>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">var principal = HttpContext.User as ClaimsPrincipal;\nvar userId = principal?.Claims\n  .First(c =&gt; c.Type == ClaimTypes.Name);\n\n_cache.Set(\"revoke-\" + userId.Value, true);\nreturn View();<\/pre>\n<p>After visiting the <code>Revoke<\/code> endpoint, the change does not have an immediate effect. After revocation, navigating home will show the debug log and redirect to Login. Note that landing in the Index page again will have a brand new auth cookie.<\/p>\n<p>To register this event, be sure to set the <code>EventsType<\/code> in the <code>CookieAuthenticationOptions<\/code>. You will need to provide a scoped service to register this <code>RevokeAuthenticationEvents<\/code>. Both are set inside the <code>ConfigureServices<\/code> method in the <code>Startup<\/code> class.<\/p>\n<p>For example:<\/p>\n<pre class=\"lang:c# theme:vs2012\">options.EventsType = typeof(RevokeAuthenticationEvents);\nservices.AddScoped&lt;RevokeAuthenticationEvents&gt;();<\/pre>\n<p>The <code>CookieValidatePrincipalContext<\/code> in <code>ValidatePrincipal<\/code> can do more than revocation if necessary. This context has <code>ReplacePrincipal<\/code> to update the principal, then renew the cookie by setting <code>ShouldRenew<\/code> to true.<\/p>\n<h2>Session Store<\/h2>\n<p>Setting a JWT in the claims to have a convenient way to access identity data works well. However, every identity claim you put in the principal ends up in the auth cookie. If you inspect the cookie, you will notice it doubles in size with an access token. As you add more claims in the principal the auth cookie gets bigger. You may hit HTTP header limits in a prod environment with many auth cookies. In IIS, the default max limit is set to 8KB-16KB depending on the version. You can increase the limit, but this means bigger payloads per request because of the cookies.<\/p>\n<p>There are many ways to quell this problem, like a user session to keep all JWTs out of the auth cookie. If you have current code that accesses the identity through the principal, then this is not ideal. Moving identity data out of the principal is risky because it may lead to a complete rewrite.<\/p>\n<p>One alternative is to use the <code>SessionStore<\/code> found in <code>CookieAuthenticationOptions<\/code>. OWIN, for example, has a similar property. Implement the <code>ITicketStore<\/code> interface and find a way to persist data in the back-end. Setting the <code>SessionStore<\/code> property defines a container to store the identity across requests. Only a session identifier gets sent to the browser in the auth cookie.<\/p>\n<p>Say you want to use in-memory persistence instead of the auth cookie:<\/p>\n<pre class=\"lang:c# theme:vs2012\">public class InMemoryTicketStore : ITicketStore\n{\n  private readonly IMemoryCache _cache;\n\n  public InMemoryTicketStore(IMemoryCache cache)\n  {\n    _cache = cache;\n  }\n\n  public Task RemoveAsync(string key)\n  {\n    _cache.Remove(key);\n\n    return Task.CompletedTask;\n  }\n\n  public Task&lt;AuthenticationTicket&gt; RetrieveAsync(string key)\n  {\n    var ticket = _cache.Get&lt;AuthenticationTicket&gt;(key);\n\n    return Task.FromResult(ticket);\n  }\n\n  public Task RenewAsync(string key, AuthenticationTicket ticket)\n  {\n    _cache.Set(key, ticket);\n\n    return Task.CompletedTask;\n  }\n\n  public Task&lt;string&gt; StoreAsync(AuthenticationTicket ticket)\n  {\n    var key = ticket.Principal.Claims\n      .First(c =&gt; c.Type == ClaimTypes.Name).Value;\n\n    _cache.Set(key, ticket);\n\n    return Task.FromResult(key);\n  }\n}<\/pre>\n<p>Set an instance of this class in <code>SessionStore<\/code> inside <code>CookieAuthenticationOptions<\/code>, these options are set in the <code>ConfigureServices<\/code> method in the <code>Startup<\/code> class. One caveat is getting an instance since it needs a provider from <code>BuildServiceProvider<\/code>. A temporary IoC container here feels hacky and pines after a better solution.<\/p>\n<p>A better approach is to use the options pattern in ASP.NET Core. Post-configuration scenarios set or change options at startup. With this solution, you leverage dependency injection without reinventing the wheel.<\/p>\n<p>To put in place this options pattern, implement\u00a0 <code>IPostConfigureOptions&lt;CookieAuthenticationOptions&gt;:<\/code><\/p>\n<pre class=\"lang:c# theme:vs2012 \">public class ConfigureCookieAuthenticationOptions\n  : IPostConfigureOptions&lt;CookieAuthenticationOptions&gt;\n{\n  private readonly ITicketStore _ticketStore;\n\n  public ConfigureCookieAuthenticationOptions(ITicketStore ticketStore)\n  {\n    _ticketStore = ticketStore;\n  }\n\n  public void PostConfigure(string name, \n           CookieAuthenticationOptions options)\n  {\n    options.SessionStore = _ticketStore;\n  }\n}<\/pre>\n<p>To register both <code>InMemoryTicketStore<\/code> and <code>ConfigureCookieAuthenticationOptions<\/code>, place this in <code>ConfigureServices<\/code>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">services.AddTransient&lt;ITicketStore, InMemoryTicketStore&gt;();\nservices.AddSingleton&lt;IPostConfigureOptions&lt;CookieAuthenticationOptions&gt;,\n  ConfigureCookieAuthenticationOptions&gt;();<\/pre>\n<p>Make sure you make this change in the <code>Startup<\/code> class. If you peek inside <code>Configure&lt;CookiePolicyOptions&gt;<\/code>, for example, and crack open the code. Note the pattern; configuration runs through a singleton object and a lambda expression. ASP.NET Core uses this same options pattern under the hood. Now, firing this up in the browser and inspecting the auth cookie will have a much smaller footprint.<\/p>\n<h2>Conclusion<\/h2>\n<p><a id=\"post-83253-_gjdgxs\"><\/a> Implementing an auth cookie is seamless in ASP.NET Core 2.1. You configure cookie options, invoke middleware, and set identity claims. Sign in and sign out methods work based on an authentication scheme. Auth cookie options allow the app to react to back-end events and set a session store. The auth cookie is flexible enough to work well with any enterprise solution.<\/p>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: Using Auth Cookies in ASP.NET Core<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. How do I set up cookie authentication in ASP.NET Core?<\/h3>\n            <div class=\"faq-answer\">\n                <p>In Program.cs (or Startup.cs), add: builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =&gt; { options.LoginPath = &#8220;\/Home\/Login&#8221;; options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.ExpireTimeSpan = TimeSpan.FromMinutes(60); }); Add app.UseAuthentication() and app.UseAuthorization() to the middleware pipeline. On successful login, create a ClaimsPrincipal with ClaimsIdentity and call HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, authProperties).<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. How do I implement cookie revocation in ASP.NET Core?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Cookie revocation invalidates active cookies server-side &#8211; useful when a user&#8217;s access is revoked before their cookie expires. The approach: add a security stamp (a GUID) to the user&#8217;s database record and include it in the cookie claims on sign-in. On each request, compare the claim value against the current database value. If they differ, the cookie is rejected and the user is signed out. Configure a custom ITicketStore or validation events with AddCookie(options =&gt; options.Events.OnValidatePrincipal = YourValidationHandler) to intercept requests and check the stamp.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. Can I include a JWT token inside an ASP.NET Core auth cookie?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Yes. After generating a JWT for downstream API calls, add it as a claim in the ClaimsIdentity: new Claim(&#8220;access_token&#8221;, jwtToken). The claim is serialized into the encrypted cookie payload and retrievable on any request with User.FindFirst(&#8220;access_token&#8221;).Value. Be aware that cookie size limits apply (4KB for the encrypted payload) &#8211; large JWTs can exceed this. If the JWT is large, store it in a server-side session store and include only a session key in the cookie claims.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. What is the difference between cookie authentication and JWT authentication in ASP.NET Core?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Cookie authentication is stateful at the client: the server issues a cookie, the browser stores and automatically sends it on every request. Best for web apps with browser clients. JWT authentication is stateless: the server issues a signed token that the client stores (localStorage, sessionStorage) and sends manually in Authorization headers. Best for APIs and mobile\/SPA clients. ASP.NET Core supports both simultaneously &#8211; a common pattern is cookie authentication for the web app with a JWT embedded in claims for downstream API calls, as demonstrated in this article.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Implement ASP.NET Core cookie authentication: configure middleware, handle login and logout, set JWT identity claims for API access, implement revocation for server-side access control, and manage session store size. Code-first guide.&hellip;<\/p>\n","protected":false},"author":274017,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":true,"footnotes":""},"categories":[143538],"tags":[],"coauthors":[41241],"class_list":["post-83253","post","type-post","status-publish","format-standard","hentry","category-dotnet-development"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/83253","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/274017"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=83253"}],"version-history":[{"count":9,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/83253\/revisions"}],"predecessor-version":[{"id":109762,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/83253\/revisions\/109762"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=83253"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=83253"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=83253"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=83253"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}