Why You Should Be Using .NET 10's New TLS Certificate

Khalid Abuhakmeh |

When developing solutions locally, our goal is to predict and adapt our code to work in a production environment. Depending on our application, predicting production can be straightforward or have several hidden quirks that can lead to hours of head-scratching debugging.

In today’s post, I want to show you a behavior happening in your local development environment that you might not be fully aware of, and how it could be impacting the understanding of your application. We’ll also explore a hidden .NET 10 feature that helps you more accurately mimic a production environment locally, thereby improving your deployment confidence when you’re ready to ship.

The Issue with localhost

Localhost is where most developers feel comfortable, and it's likely one of the most frequently mentioned terms for ASP.NET Core developers. It permeates conversations, documentation, and templates, but it hides a not-so-obvious issue: It’s a single domain. Why is this an issue?

Let’s read the Mozilla documentation on cookies and see how cookies relate to domains.

“The Domain and Path attributes define the scope of a cookie: what URLs the cookies are sent to… The Domain attribute specifies which server can receive a cookie. If specified, cookies are available on the specified server and its subdomains. For example, if you set Domain=mozilla.org from mozilla.org, cookies are available on that domain and subdomains like developer.mozilla.org.” – Mozilla Documentation.

For folks developing multiple applications locally, they can already see the issue. Any cookies set at localhost will be passed and processed by any other ASP.NET Core application hosted on the local domain. Cookies can bleed from one application to another, causing a string of issues:

  • Cookie name conflicts and overwrites
  • Behavioral changes
  • Slow requests as all cookies are parsed and processed
  • Bad Requests as cookies overwhelm the size limits

It’s not good. At best, this issue can leave you spending precious time clearing cookies, and at worst, spending days debugging confusing bugs that only manifest in environments that share the same domain but host multiple applications.

To illustrate the localhost issue, let’s build two web applications that both rely on accessing a cookie. Let’s start with the first ASP.NET Core application. In a new Empty template, let’s create two endpoints.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", (HttpContext ctx) =>
{
    ctx.Response.Cookies.Append("cookie", "chocolate chip", new ()
    {
        HttpOnly = true,
        Secure = true,
        SameSite = SameSiteMode.Strict
    });
    
    return TypedResults.Text(
        // lang=html
        """
        <html lang="en">
            <body>
                <h1>Cookie Jar</h1>
                <a href="/cookie">Cookie</a>
            </body>
        </html>
        """, 
        "text/html; charset=utf-8");
});

app.MapGet("/cookie", (HttpContext ctx) =>
{
    var cookie = ctx.Request.Cookies["cookie"];
    
    return TypedResults.Text(
        // lang=html
        $"""
        <html lang="en">
            <body>
                <h1>{cookie}</h1>
                <a href="/">Home</a>
            </body>
        </html>
        """, 
        "text/html; charset=utf-8");
});

app.Run();

This application will run on localhost:7014.

"https": {
  "commandName": "Project",
  "dotnetRunMessages": true,
  "launchBrowser": true,
  "applicationUrl": "https://localhost:7014;http://localhost:5138",
  "environmentVariables": {
    "ASPNETCORE_ENVIRONMENT": "Development"
  }
}

Upon starting the application, we can see that it sets a cookie in the client, which we can access on the /cookie endpoint.

First application in ASP.NET Core 10

As expected. Let’s create a new web application using the same Empty template.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", (HttpContext ctx) =>
{
    var cookie = ctx.Request.Cookies["cookie"] ?? "No Cookies For You!";
    
    return TypedResults.Text(
        // lang=html
        $"""
         <html lang="en">
             <body>
                 <h1>🍪 Cookie Jar Two</h1>
                 <h2>{cookie}</h2>
             </body>
         </html>
         """, 
        "text/html; charset=utf-8");
});

app.Run();

This application will run on localhost:7128 as seen in the launchSettings.json file.

"https": {
  "commandName": "Project",
  "dotnetRunMessages": true,
  "launchBrowser": true,
  "applicationUrl": "https://localhost:7128;http://localhost:5219",
  "environmentVariables": {
    "ASPNETCORE_ENVIRONMENT": "Development"
  }
}

Let’s start this application and see what happens.

Second application in ASP.NET Core 10

Oh no, what just happened?! Well, based on the Mozilla documentation, any app hosted on localhost domain can access any cookie written with a matching domain. We can actually see the Domain value using our browser’s dev tools.

Access to cookies in browser developer tools

How do we solve this issue? Well, .NET 10 to the rescue.

.NET 10’s Localhost Certificate

When you install .NET 10, the first use of the CLI will ask you to install the new developer certificate, the one used by ASP.NET Core applications to provide HTTPS support. If not prompted, you can install the latest certificate using the following commands.

dotnet dev-certs https --clean
dotnet dev-certs https --trust

The commands should install the certificate to your operating systems certificate store.

Install .NET 10 TLS certificate in certificate store

You’ll notice something interesting about the latest certificate. It has a few more DNS entries: .dev.localhost, *.dev.internal, host.docker.internal, and host.containers.internal.

A new .NET 10 feature allows you to use this certificate to run local applications with a unique domain name, as long as it ends in one of these domains. Let’s update our launchSettings.json file to give each application a unique domain name. The names should somewhat match your application name, but be careful not to include any . values, as they’ll become a new subdomain that is unsupported by the certificate.

"https": {
  "commandName": "Project",
  "dotnetRunMessages": true,
  "launchBrowser": true,
  "applicationUrl": "https://cookiejarsone.dev.localhost:7014",
  "environmentVariables": {
    "ASPNETCORE_ENVIRONMENT": "Development"
  }
}

And for the second application:

"https": {
  "commandName": "Project",
  "dotnetRunMessages": true,
  "launchBrowser": true,
  "applicationUrl": "https://cookiejarstwo.dev.localhost:7128",
  "environmentVariables": {
    "ASPNETCORE_ENVIRONMENT": "Development"
  }
}

Note that the port value is essential, since these applications are still running locally, and a unique port is necessary to avoid hosting conflicts. Let’s run both applications and see what behavior we see now.

Cookies on separate localhost domain

Cookies are now working as you’d expect across applications, with them being isolated correctly to the application. As you can see in the screenshot, the Domain value matches that of the application, and is cookiejarsone.dev.localhost.

What’s the BIG Deal?!

For us at Duende, authentication relies heavily on cookies, whether that’s IdentityServer or our latest product, the Backend for Frontend Security Framework. For ASP.NET Core developers, cookies are a vital part of development, and having access to domain names that help us mimic a production environment is a step in the right direction.

With this feature built in as part of .NET 10, it gives developers more confidence that what they’re building and testing locally will behave similarly in staging and production environments.

Conclusion

While the latest update to the certificate may feel small compared to all the changes coming in .NET 10, this one should make ASP.NET Core development more straightforward and predictable, especially for folks building authentication solutions with Duende IdentityServer.

If you have a solution locally, we recommend updating your launchSettings.json to use this latest feature, as it will avoid one less issue when implementing security or upgrading your existing applications to .NET 10.

As always, please share your thoughts in the comments section below and let us know about your experience with cookies. We’d love to hear about them.