Most of what I write about .NET is about the security side of ASP.NET Core: how a user gets signed in, what ends up in the cookie or the token, and why it sometimes breaks. This page puts those posts in a sensible reading order. All my .NET posts are listed at the bottom.

What Is ASP.NET Core?

ASP.NET Core is Microsoft's open-source, cross-platform framework for building web applications and APIs on .NET. Every request passes through a pipeline of middleware. Authentication, authorization, cookies and forwarded headers are all handled by middleware and handlers in that pipeline, so the order and the configuration of that pipeline decide whether your users get signed in.

The ASP.NET Core request pipeline: the web server creates the HttpContext, and the request passes middleware, authentication with the JwtBearer handler and authorization before it reaches MVC.

Why I Write About the Security Side

I have worked with .NET since 2002. When something in ASP.NET Core does not behave the way I expect, I download the framework source and step through it until I understand what happens. Many of the posts below started that way. Authentication is the part customers find hardest, so that is what I write about most.

What Goes Wrong in Practice

The same problems come up again and again, in questions from developers, in my workshops and in client projects:

  • The browser rejects the cookie. A cookie with SameSite=None must also be Secure, so sign-in over plain HTTP fails in ways that are easy to miss. Chrome's DevTools show which cookie was blocked and why.
  • The authentication cookie grows too big. Add OpenID Connect and the tokens, and ASP.NET Core splits the cookie into chunks. A session store keeps the cookie small and lets you end a session on the server.
  • The API returns 401 or 403, and nobody knows why. A 401 comes from the JwtBearer handler: the token is missing or not accepted. A 403 comes from authorization: the token is fine, but a claim or policy is not. The WWW-Authenticate header and the logs usually tell you which.
  • Claims are renamed or missing. By default, ASP.NET Core maps incoming claim names to the long Microsoft claim types, so role or name is not where your code looks for it.
  • The app is behind a proxy and does not know it. Without the Forwarded Headers Middleware, ASP.NET Core sees the proxy's IP address, scheme and host instead of the client's. That gives you broken links and the wrong IP address in your logs.
  • The Data Protection key ring is not persisted. The keys protect your cookies, so a restart, a redeploy or a second instance signs everyone out.

Most of these come down to not knowing what goes over the wire. I use Fiddler a lot, and many of the posts below show the actual HTTP traffic.

Sign-In, Tokens and the BFF Pattern

Three of the bigger topics have their own pages, with a reading list each:

  • IdentityServer: what Duende IdentityServer is, how to set it up, and how to run it in Docker.
  • OpenID Connect and OAuth: the login flow seen from ASP.NET Core: state and nonce, claims, PAR and custom parameters.
  • The BFF pattern: how to keep tokens out of single-page apps with a Backend-for-Frontend in ASP.NET Core.

Start Here

1. Cookies and how sign-in works

2. Protect your APIs with JwtBearer

3. Run behind a reverse proxy

4. Keep the Data Protection keys safe

5. Other .NET posts

Frequently Asked Questions

Why does my ASP.NET Core API return 401 Unauthorized?

A 401 usually comes from the JwtBearer handler, and it means the access token was missing or not accepted. Look at the WWW-Authenticate response header first. Set IncludeErrorDetails to true in the JwtBearer options, and the header tells you why, for example that the signature is invalid. If you get 403 instead, the token was accepted and the problem is in authorization. My post Troubleshooting JwtBearer authentication issues goes through it step by step.

Why are my claims renamed or missing in ASP.NET Core?

By default, the JwtBearer and OpenID Connect handlers map short claim names such as role to long Microsoft claim type URIs. Set MapInboundClaims to false to keep the original names, and tell ASP.NET Core which claims hold the user's name and roles. For APIs, see Debugging JwtBearer Claim Problems. For sign-in, see Debugging OpenID Connect Claim Problems.

What does ASPNETCORE_FORWARDEDHEADERS_ENABLED do?

When it is set to true, ASP.NET Core adds the Forwarded Headers Middleware at the very start of the pipeline for you, so the app uses the client's IP address and scheme from the X-Forwarded-* headers. I show it in Configuring ASP.NET Core Forwarded Headers Middleware, together with the settings you should check when you configure the middleware yourself.

Why are my users signed out after a restart or a redeploy?

The authentication cookie is protected with keys from the Data Protection key ring. If the keys are only kept in memory or inside the container, they are lost on restart, and a second instance has different keys. Store the key ring somewhere shared and persistent. I show one way in Persisting the Data Protection Key Ring in Azure Key Vault.

Should I use cookies or tokens in my ASP.NET Core app?

For a server-rendered web app, the Cookie handler is the natural choice. For APIs, use JwtBearer with access tokens. For single-page apps, I recommend the BFF pattern, so the browser holds a secure cookie and the tokens stay on the server. My post on the BearerToken handler compares the three handlers.

Want to Go Further?

If you want to learn how authentication works inside ASP.NET Core, see my Dometrain course Getting Started: Authentication and Authorization in .NET. It covers the ClaimsPrincipal, the authentication middleware, the cookie handler, Data Protection and authorization, with Fiddler showing the traffic along the way.

If your team needs help with authentication or security in ASP.NET Core, you can hire me.

All .NET Posts