-
The field Keyword in C# 14: Write Less, Validate More
You need a property that validates its input. In C# 13 and earlier, that means writing a private backing field, a get accessor that returns it, and a set accessor that validates the value before storing it. Three moving parts for one property:
public class Greeting { private string _msg = "Hello"; public string Message { get => _msg; set => _msg = value ?? throw new ArgumentNullException(nameof(value)); } }One property, one validation rule, three lines of ceremony. The backing field
_msgexists only to givesetsomething to write to andgetsomething to read from. It carries no meaning of its own.Multiply this across a configuration class with five or six validated properties, and the noise adds up fast.
-
The Real Cost of Build vs. Buy for Identity
Imagine a development team starting to build their own auth system from scratch. Talented developers, clean architecture, all the right OAuth 2.0 flows. When they succeed, they are proud of it, and they should be, because it works.
Months later, three sprints deep into adding SAML for a single enterprise customer. Their best Auth engineer had just given two weeks' notice. The compliance team is asking questions about session management that no one else on the team can answer. And the original six-week project? It had quietly become the single largest line item in their technical debt backlog.
"We'll just build it ourselves" is one of the most common and most expensive sentences in software engineering. The logic seems sound on the surface. Your team knows your requirements. You don't want vendor lock-in. You've got talented people. A few sprints, maybe a quarter, and you'll have exactly what you need.
Here's the thing nobody tells you during that planning meeting: the initial build is the cheap part. It's the years that follow — maintenance, security patching, protocol updates, compliance audits, incident response, and the features you didn't know you'd need — that quietly bleed budgets. And when things go truly wrong, the costs aren't measured in sprints. They're measured in breach notifications and lost customer trust.
This post breaks down the real total cost of ownership for identity systems, using data from the IBM/Ponemon Cost of a Data Breach Report and the Verizon DBIR. We'll be fair about when building makes sense. But we'll also make the case for a third option that most teams don't consider early enough, and it's where the smartest ones tend to end up.
-
OAuth 2.1 Made Simple: The Only Flows You Need
Back in 2019, Dominick Baier, Duende Cofounder and Security subject-matter expert, wrote a prophetic post called "Two is the Magic Number", a riff on De La Soul's "Three is the Magic Number", arguing that you only needed two OAuth flows to cover every real-world scenario. At the time, it was a bold simplification. OAuth 2.0 had shipped with a sprawl of grant types: Implicit, Resource Owner Password Credentials, Authorization Code without PKCE, Client Credentials, and the ecosystem dutifully built tutorials for all of them. The result was confusion. Developers picked the wrong flow, shipped insecure apps, and blamed OAuth itself for being "too complicated."
Dominick was right, and the standards body agreed. OAuth 2.1 formally removed the footguns, mandated PKCE on every authorization code grant, and left us with a protocol that is dramatically simpler to learn and harder to misuse. If you're building on .NET 10 in 2026, this is the only article you need. Two flows cover almost everything. A third handles the edge case. Let's go.
-
Beyond localhost: Multi-Instance ASP.NET Core Deployment with .NET 10
Everything works great on your machine. One instance, one process, one set of keys in memory. Then you scale to two instances, maybe a Kubernetes deployment rolling out replicas, maybe an IIS web farm, and suddenly users are getting logged out mid-session, anti-forgery tokens stop validating, and cached data is inconsistent depending on which server answers the request. Help!
It's almost always the same handful of things. You built your app for a single process, and when you run it in multiple processes, those assumptions break silently.
This post walks through the practices that keep ASP.NET Core apps well-behaved across multiple instances.
-
Harden Your .NET JSON Deserialization with System.Text.Json and JsonSerializerOptions.Strict
Consider the following JSON payload hitting your API:
{"Amount": 100, "Amount": -999}Two properties with the same name. RFC 8259 Section 4 says object names "SHOULD be unique," and warns that parser behavior is unpredictable when they aren't. System.Text.Json takes the permissive path: last-write-wins, no warning, no error.
using System.Text.Json; string duplicateJson = """{"Amount": 100, "Amount": -999}"""; var payment = JsonSerializer.Deserialize<Payment>(duplicateJson); Console.WriteLine($"Amount = {payment!.Amount}"); // Output: Amount = -999 public record Payment(int Amount);The attacker's value wins silently. This isn't just a duplicate property quirk. Default deserialization also ignores extra fields an attacker might inject, lets null slide into non-nullable properties, and skips over missing required data. Each of these "conveniences" is a potential security gap at your API boundary.
-
ASP.NET Core Cookie Size Limits in Production: Causes and Fixes
Everything works fine in development. You log in, you get a cookie, life is good. You deploy to production, and a portion of your users can't authenticate. No useful error message. The browser just silently fails or shows a generic "something went wrong" page. Your logs contain
431 Request Header Fields Too Largeor requests that arrive with a valid session cookie that your app promptly rejects.The culprit is almost always cookie size. This post walks through why auth cookies grow out of control, how to spot the symptoms, and concrete solutions you can apply today.
-
The Emergency Stop Button - Implementing Immediate Token Revocation in .NET 10
Imagine this sweat-inducing nightmare scenario. A banking customer's phone is stolen, and your mobile app is logged in, granting the thief complete access to their account. A frantic call comes into support. Every second counts. What is your speed-to-response for revoking that active session and securing their funds?
If you're relying on standard self-contained JWTs, the honest answer might be "up to an hour", depending on how long the token is valid. That's not going to cut it. Let's talk about how Reference Tokens give you an emergency stop button for exactly these situations, and how to wire it all up with Duende IdentityServer in .NET 10.
-
The 2025 OWASP Top 10 and IdentityServer
When the first OWASP Top 10 list was released in 2003, no one knew that subsequent editions of the top 10 security risks for web applications would become a de facto standard, used and referenced around the world. A Google Scholar search for references to the list yields tens of thousands of results, and helped establish standards such as PCI DSS (Payment Card Industry Data Security Standard), while several national security bodies reference it directly.
After the initial 2003 version, the next edition came one year later. Then the project first switched to a three-year cadence (2007, 2010, 2013) and later to a four-year cadence (2017, 2021). If you do the math, you may have already guessed that a new edition of the OWASP Top 10 was due for 2025, and the project team delivered! Just in time, you might say, since the final version was not released before Dec 24, 2025, but now we have the 2025 OWASP Top 10. Let's have a look at how the list has been assembled, its relevance today, and of course, what's in the list!
-
Update Guidance for CVE-2026-40372 - ASP.NET Data Protection
On April 21, 2026, Microsoft disclosed CVE-2026-40372, a high-severity vulnerability in the
Microsoft.AspNetCore.DataProtectionNuGet package. It allows attackers to execute an Elevation of Privilege attack by forging authentication cookies.None of the Duende packages have a direct or transitive dependency on the
Microsoft.AspNetCore.DataProtectionNuGet package. However there are cases where you may be using that NuGet package as a dependency within your application. -
Why a Standard JWT Access Token Matters
When OAuth 2.0 was published as RFC 6749 in 2012, it was deliberately silent on the format of access tokens. The spec described them as opaque strings: the client receives one, attaches it to API requests, and never looks inside. That abstraction was intentional, but it created a vacuum.
As the industry adopted JWTs for access tokens (a practice accelerated by the publication of the JWT specification itself as RFC 7519 in 2015), every identity provider filled that vacuum in its own way. Auth0 puts scopes in an array. Azure AD used
rolesclaims and application ID URIs. IdentityServer emitted individualscopeclaims. Okta had its own structure. The token was always a JWT, but beyond that, nothing was consistent.The consequences were predictable. If you built an API that consumed tokens from a single provider, you wrote validation logic tailored to that provider's JWT structure. If your organization then migrated providers, or if you needed to accept tokens from multiple issuers (a common scenario in B2B integrations and microservice architectures), you had to write custom parsing logic for each one.
Claim names varied. Scope formats clashed. Some providers included an audience claim; others didn't. Some set a
typheader; most left it as the defaultJWT. Every integration was a bespoke affair, and every bespoke affair was a surface for security bugs.RFC 9068, published in September 2021, ended the guesswork. It defines a concrete JWT profile for OAuth 2.0 access tokens: which claims must be present, how they're formatted, and how validators should process them. It gives us a common language. When your identity provider issues an RFC 9068-compliant access token and your API validates one, both sides agree on the structure: the
typheader readsat+jwt, thescopeclaim is a space-delimited string, theaudclaim names the intended API, and so on. The timeline from silence to standard took nearly a decade: OAuth 2.0 in 2012, JWT in 2015, the draft profile in 2019, and the finalized RFC in 2021.Today, every major identity provider supports it, and there is no longer a good reason to invent your own access token format.