Wednesday, 8 October 2025

Support for Cross-Platforms in ASP.NET Core

Leave a Comment

ASP.NET Core is a revolutionary web framework from Microsoft that enables developers to build high-performance, modern web applications that can run on Windows, Linux, and macOS . Its cross-platform support is one of the most transformative aspects of the framework, allowing developers to create applications that are platform-independent, lightweight, and cloud-ready. Let’s explore how ASP.NET Core achieves this level of cross-platform compatibility, its architecture, tools, and examples of how developers can leverage it to build and deploy apps seamlessly across different operating systems.

The Evolution Towards Cross-Platform

Traditionally, ASP.NET applications were tied to the Windows operating system and the .NET Framework, limiting flexibility in deployment and hosting options. This changed with the introduction of .NET Core (now unified under the .NET platform ).

ASP.NET Core was rewritten from scratch to be:

  • Modular and lightweight

  • Open-source and community-driven

  • Platform-independent

  • Cloud-optimized

This means developers can now build once and deploy anywhere — whether on Windows servers, Linux containers, or macOS environments.

2. Cross-Platform Architecture

ASP.NET Core achieves platform independence through a layered architecture :

  • .NET Runtime: Executes code on different OSes.

  • .NET SDK: Provides build and deployment tools for all platforms.

  • Kestrel Web Server: A lightweight, cross-platform web server included by default.

  • Integration with Nginx/Apache: Enables hosting on Linux systems with reverse proxies.

Here’s a simple visual representation:

+---------------------------+
|       ASP.NET Core        |
+---------------------------+
|     .NET Runtime Layer    |
+---------------------------+
|     SDK & CLI Tools       |
+---------------------------+
| Hosting: IIS | Nginx | Kestrel |
+---------------------------+
| Windows | Linux | macOS |
+---------------------------+

This structure ensures that your ASP.NET Core application can run smoothly on any OS that supports the .NET runtime.

3. Cross-Platform Command-Line Tools

One of the key enablers of ASP.NET Core’s cross-platform development is the .NET CLI (Command Line Interface). It allows developers to create, build, run, and publish applications directly from the terminal on any operating system.

Example Commands

Create a new ASP.NET Core Web App

dotnet new webapp -o MyWebApp

Run the application

cd MyWebApp
dotnet run

Publish for Linux

dotnet publish -c Release -r linux-x64 --self-contained

These commands behave identically on Windows, macOS, or Linux , showcasing the framework’s uniformity.

4. Writing a Cross-Platform ASP.NET Core Application

Here’s an example of a simple ASP.NET Core Web API that runs seamlessly on any platform.

Program.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;

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

app.MapGet("/", () => "Hello, ASP.NET Core Cross-Platform World!");

app.Run();
Run on Windows
dotnet run

Output:

Now listening on: http://localhost:5000
Run on Linux or macOS

Same command, same output.
You can test it by visiting:
http://localhost:5000 in any browser.

This example demonstrates that no OS-specific changes are required. The same source code runs identically everywhere.

5. Cross-Platform Hosting and Deployment

ASP.NET Core applications can be hosted using multiple approaches:

a. Kestrel Web Server

Kestrel is a built-in, cross-platform web server that serves as the backbone for hosting ASP.NET Core apps. It’s fast, lightweight, and designed for both development and production.

Example appsettings.json configuration:

{"Kestrel": {
"Endpoints": {
  "Http": {
    "Url": "http://0.0.0.0:5000"
  }
}}}
b. Reverse Proxy with Nginx (Linux)

You can host ASP.NET Core apps on Linux with Nginx as a reverse proxy to Kestrel.

Example Nginx Configuration:

server {
    listen 80;
    server_name myapp.com;

    location / {
        proxy_pass         http://localhost:5000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

This setup allows you to take advantage of Linux’s stability and performance while running ASP.NET Core.

6. Self-Contained and Framework-Dependent Deployments

ASP.NET Core supports two major deployment models, enhancing its cross-platform flexibility:

Framework-Dependent Deployment (FDD)

The application uses the system-wide installed .NET runtime.

dotnet publish -c Release

Self-Contained Deployment (SCD)

The .NET runtime and all dependencies are bundled with your app, allowing it to run even on systems without .NET installed.

dotnet publish -c Release -r linux-x64 --self-contained true

This approach is ideal for Linux servers or containerized environments.

7. Cross-Platform Development Environments

You can build ASP.NET Core apps using a variety of tools on different platforms:

Operating SystemIDE/Editor OptionsExample
WindowsVisual Studio, VS Code, RiderFull feature-rich experience
macOSVisual Studio for Mac, VS Code, RiderDebugging, testing, publishing
LinuxVisual Studio Code, Rider, CLI toolsLightweight and efficient

For instance, on Linux , you can install .NET SDK and start developing directly from the terminal:

sudo apt-get update
sudo apt-get install -y dotnet-sdk-8.0

Then use:

dotnet new mvc -o CrossPlatformApp
cd CrossPlatformApp
dotnet run

8. Cross-Platform Testing and CI/CD

ASP.NET Core integrates smoothly with cross-platform CI/CD pipelines using tools like:

  • GitHub Actions

  • Azure DevOps

  • Jenkins

  • GitLab CI

Example GitHub Action Workflow

name: Build and Test ASP.NET Core App

on: [push]

jobs:build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: 8.0.x
      - name: Restore dependencies
        run: dotnet restore
      - name: Build
        run: dotnet build --configuration Release
      - name: Test
        run: dotnet test --no-build --verbosity normal

This configuration builds and tests your ASP.NET Core app on Linux (Ubuntu), but can just as easily run on Windows or macOS agents — truly cross-platform automation.

9. Containers and Cloud Deployments

ASP.NET Core’s portability makes it perfect for containerized and cloud-native applications.

Dockerfile Example

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyWebApp.dll"]

You can build and run it on any platform that supports Docker:

docker build -t mywebapp .
docker run -d -p 8080:80 mywebapp

This container can then be deployed to Azure , AWS , or Google Cloud , independent of the underlying OS.

10. Benefits of Cross-Platform ASP.NET Core

  • Freedom of Choice: Use any OS, IDE, or cloud provider.

  • Cost Savings: Host apps on Linux servers, which are often cheaper.

  • Performance: The Kestrel server and runtime optimizations ensure fast execution.

  • DevOps Friendly: Unified CI/CD pipelines work on all major platforms.

  • Open Source Ecosystem: Continuous improvements from Microsoft and the global community.

11. Real-World Applications

Many global companies leverage ASP.NET Core for cross-platform projects, including:

  • Stack Overflow

  • Alibaba Cloud

  • Siemens

  • GoDaddy

These organizations benefit from improved performance, reduced infrastructure costs, and easier scalability.

Conclusion

ASP.NET Core’s cross-platform support empowers developers to build and deploy web applications that are truly platform-agnostic. Whether you’re developing on Windows, deploying to Linux containers, or testing on macOS, the framework ensures consistency, performance, and reliability across all environments.

By combining its modular architecture, cross-platform SDK, Kestrel web server, and seamless container integration, ASP.NET Core has redefined modern web development — proving that the future of software is “build anywhere, run everywhere.”

ASP.NET Core 10.0 Hosting Recommendation

One of the most important things when choosing a good ASP.NET Core 9.0 hosting is the feature and reliability. HostForLIFE is the leading provider of Windows hosting and affordable ASP.NET Core, their servers are optimized for PHP web applications. The performance and the uptime of the hosting service are excellent and the features of the web hosting plan are even greater than what many hosting providers ask you to pay for. 

At HostForLIFE.eu, customers can also experience fast ASP.NET Core hosting. The company invested a lot of money to ensure the best and fastest performance of the datacenters, servers, network and other facilities. Its datacenters are equipped with the top equipments like cooling system, fire detection, high speed Internet connection, and so on. That is why HostForLIFEASP.NET guarantees 99.9% uptime for ASP.NET Core. And the engineers do regular maintenance and monitoring works to assure its Orchard hosting are security and always up.

Read More...

Thursday, 2 October 2025

Simple Examples to Help You Understand Dependency Injection (DI) in.NET Core

Leave a Comment

Writing code that is flexible, easy to maintain, and easy to test is crucial for developing contemporary apps. Dependency Injection (DI) assists us in doing just that. DI makes the code clearer and easier to maintain by allowing dependencies to be supplied from the outside rather than requiring a class to create everything it needs on its own.

 

In this article, we will learn:
  • What is Dependency Injection?

  • Why do we need it?

  • A simple example (Car and Engine)

  • A real-world example ( ILogger<T> in .NET Core)

  • What Happens Behind the Scenes with ILogger<T>

  • Types of DI and Service Lifetimes

  • Benefits of using DI

What is Dependency Injection?

Dependency Injection (DI) is a design pattern where a class receives its dependencies from the outside , instead of creating them inside.

In simple words:

  • A Car needs an Engine

  • Instead of the car creating the Engine, we inject the Engine from outside.

This makes code flexible, maintainable, and testable .

Without DI (Tight Coupling):

public class Engine
{
    public string Start() => "Engine started.";
}

public class Car
{
    private Engine _engine = new Engine(); // Car is tightly bound to Engine

    public string Run()
    {
        return _engine.Start() + " Car is running.";
    }
}

Problem: If we want to use a PetrolEngine or ElectricCar, we must modify the Car class. This breaks the Open/Closed Principle (OCP) of SOLID.

With Dependency Injection (Loose Coupling)

Step 1. Create an Interface

public interface IEngine
{
    string Start();
}

Step 2. Implement Engines

public class PetrolEngine : IEngine
{
    public string Start() => "Petrol engine started.";
}

public class ElectricEngine : IEngine
{
    public string Start() => "Electric engine started.";
}

Step 3. Inject Engine into Car

public class Car
{
    private readonly IEngine _engine;

    // Constructor Injection
    public Car(IEngine engine)
    {
        _engine = engine;
    }

    public string Run()
    {
        return _engine.Start() + " Car is running.";
    }
}

Step 4. Register in Program.cs

var builder = WebApplication.CreateBuilder(args);

// Registering the DI services
builder.Services.AddScoped<IEngine, ElectricEngine>();
builder.Services.AddScoped<Car>();

var app = builder.Build();

app.MapGet("/run", (Car car) => car.Run());

app.Run();

Now, if you hit /run, you will see:
Electric engine started. Car is running.

If tomorrow you want a petrol engine, just change the registration line:

// ElectricEngine can be replaced with PetrolEngine easily.
builder.Services.AddScoped<IEngine, PetrolEngine>();
Real-World Example: ILogger<T> in .NET Core

One of the best practical uses of DI is logging.

ILogger<T> in .NET Core is a built-in service that helps you write logs for your application, like messages, warnings, or errors. You don’t have to create it yourself—.NET Core provides it automatically using dependency injection. The <T> tells the logger which class it’s coming from, so when you look at the logs, you can easily see where each message originated. It’s a simple and clean way to keep track of what’s happening in your app while keeping your code organized.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger) // Constructor Injection: ILogger is passed in automatically

    {
        _logger = logger;
    }

    [HttpGet("welcome")]
    public string GetWelcome()
    {
         // Using the injected logger
        _logger.LogInformation("Welcome Rudra! endpoint was called!");
        return "Hello, Dependency Injection in .NET Core!";
    }
}

Here, ILogger<HomeController> is automatically injected by the DI container.
No need to create a logger manually i.e. .NET handles it for us.

When you call /home/welcome, you’ll see a log like this in the console:

info: MyApp.Controllers.HomeController[0]
       Welcome endpoint was called!

Important: What Happens Behind the Scenes with ILogger<T>

When you access the URL /home/welcome in your browser, ASP .NET Core creates a new instance of the HomeController. It notices that the controller’s constructor requires an ILogger<HomeController>. At this point, it asks the Dependency Injection (DI) container:

Do we have a service for ILogger<HomeController>?”

The DI container already has it registered internally, so it automatically provides an instance of ILogger<HomeController> to the controller. When the GetWelcome() method is called, the log message is sent to the console (or whichever logging provider is configured).

By default, in .NET 6/7/8 Web API, you’ll see something like this in your console:

info: MyApp.Controllers.HomeController[0]
      Welcome endpoint was called!

This output comes directly from the code:

_logger.LogInformation("Welcome endpoint was called!");

The key point here is that you never manually create the logger and you don’t have to configure how the logs are written—the DI container does it for you. Instead of the class being responsible for creating its own dependencies, it simply asks for them in the constructor. This is exactly what Dependency Injection is all about: your class declares what it needs, and the framework provides it automatically.

Types of Dependency Injection

1. Constructor Injection (Most Common)

We already followed constructor injection in example above. In this approach, dependencies are passed into a class through its constructor. It ensures that the dependency is available as soon as the object is created. This is the most widely used method in .NET Core and is also considered best practice.

2. Method Injection

Method Injection is when a class receives a dependency only when a specific method is called, instead of getting it through the constructor or a property. In other words, the dependency is passed as a parameter to the method that needs it. This is useful when the dependency is needed only occasionally or for certain actions. However, if multiple methods need the same dependency, you may end up passing it repeatedly, so it’s less commonly used than constructor injection.

3. Property Injection

Property Injection allows you to provide a class with its dependency through a public property instead of the constructor. In other words, you first create the object and then assign the dependency to its property. This can be useful when the dependency is optional or only needed later. However, you need to be careful—if the property is not set before using it, it can cause errors, which is why this approach is used less often than constructor injection.

Service Lifetimes in .NET Core

Service lifetimes in .NET Core determine how long a dependency object lives when it’s provided by the DI container. There are three main types:

  • Singleton: A single instance is created and shared across the entire application. For example, a configuration service that never changes can be registered as singleton.

  • Scoped: A new instance is created for each HTTP request. For example, a user session service can be scoped so each request gets its own instance.

  • Transient: A new instance is created every time the dependency is requested. For example, a lightweight helper service that performs simple calculations can be transient.

Choosing the right lifetime ensures efficient resource use, predictable behavior, and cleaner code while using dependency injection effectively.

builder.Services.AddSingleton<IEngine, PetrolEngine>();  // same instance for whole app
builder.Services.AddScoped<IEngine, PetrolEngine>();     // one per request
builder.Services.AddTransient<IEngine, PetrolEngine>();  // new every time

Benefits of Dependency Injection

  • Makes code cleaner and easier to maintain.

  • Reduces tight coupling between classes.

  • Allows you to swap implementations without changing your code.

  • Makes unit testing easier by enabling mock dependencies.

  • Leads to organized, reusable, and testable code.

  • Helps build scalable and flexible applications, especially in large projects.

Conclusion

Dependency Injection (DI) is a powerful design pattern in .NET Core that helps make your applications cleaner, more maintainable, and easier to test. By letting the framework provide the dependencies, you reduce tight coupling, improve flexibility, and can easily swap implementations without changing your classes. We saw how DI works with a simple Car and Engine example and also in a real-world scenario using ILogger<T>, along with the different service lifetimes—Singleton, Scoped, and Transient. Understanding and using DI effectively is a key step toward writing professional, robust, and scalable .NET Core applications.

What’s Next?

In the next article, we will dive into Microservices in .NET Core. You’ll learn how to design and implement small, independent services that work together to form a larger application. We’ll also go through a practical example to see how microservices communicate, how to manage dependencies, and how DI plays a role in building scalable and maintainable applications.

In this article, we will learn:

  • What is Dependency Injection?

  • Why do we need it?

  • A simple example (Car and Engine)

  • A real-world example ( ILogger<T> in .NET Core)

  • What Happens Behind the Scenes with ILogger<T>

  • Types of DI and Service Lifetimes

  • Benefits of using DI

What is Dependency Injection?

Dependency Injection (DI) is a design pattern where a class receives its dependencies from the outside , instead of creating them inside.

In simple words:

  • A Car needs an Engine

  • Instead of the car creating the Engine, we inject the Engine from outside.

This makes code flexible, maintainable, and testable .

Without DI (Tight Coupling):
public class Engine
{
    public string Start() => "Engine started.";
}

public class Car
{
    private Engine _engine = new Engine(); // ❌ Car is tightly bound to Engine

    public string Run()
    {
        return _engine.Start() + " Car is running.";
    }
}

Problem: If we want to use a PetrolEngine or ElectricCar, we must modify the Car class. This breaks the Open/Closed Principle (OCP) of SOLID.

With Dependency Injection (Loose Coupling)

Step 1. Create an Interface

public interface IEngine
{
    string Start();
}
Step 2. Implement Engines
public class PetrolEngine : IEngine
{
    public string Start() => "Petrol engine started.";
}

public class ElectricEngine : IEngine
{
    public string Start() => "Electric engine started.";
}
Step 3. Inject Engine into Car
public class Car
{
    private readonly IEngine _engine;

    // Constructor Injection
    public Car(IEngine engine)
    {
        _engine = engine;
    }

    public string Run()
    {
        return _engine.Start() + " Car is running.";
    }
}
Step 4. Register in Program.cs
var builder = WebApplication.CreateBuilder(args);

// Registering the DI services
builder.Services.AddScoped<IEngine, ElectricEngine>();
builder.Services.AddScoped<Car>();

var app = builder.Build();

app.MapGet("/run", (Car car) => car.Run());

app.Run();

Now, if you hit /run, you will see:
Electric engine started. Car is running.

If tomorrow you want a petrol engine, just change the registration line:

// ElectricEngine can be replaced with PetrolEngine easily.
builder.Services.AddScoped<IEngine, PetrolEngine>();
Real-World Example: ILogger<T> in .NET Core

One of the best practical uses of DI is logging.

ILogger<T> in .NET Core is a built-in service that helps you write logs for your application, like messages, warnings, or errors. You don’t have to create it yourself—.NET Core provides it automatically using dependency injection. The <T> tells the logger which class it’s coming from, so when you look at the logs, you can easily see where each message originated. It’s a simple and clean way to keep track of what’s happening in your app while keeping your code organized.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger) // Constructor Injection: ILogger is passed in automatically

    {
        _logger = logger;
    }

    [HttpGet("welcome")]
    public string GetWelcome()
    {
         // Using the injected logger
        _logger.LogInformation("Welcome Rudra! endpoint was called!");
        return "Hello, Dependency Injection in .NET Core!";
    }
}

Here, ILogger<HomeController> is automatically injected by the DI container.
No need to create a logger manually i.e. .NET handles it for us.

When you call /home/welcome, you’ll see a log like this in the console:

info: MyApp.Controllers.HomeController[0]
       Welcome endpoint was called!
Important: What Happens Behind the Scenes with ILogger<T>

When you access the URL /home/welcome in your browser, ASP .NET Core creates a new instance of the HomeController. It notices that the controller’s constructor requires an ILogger<HomeController>. At this point, it asks the Dependency Injection (DI) container:

Do we have a service for ILogger<HomeController>?”

The DI container already has it registered internally, so it automatically provides an instance of ILogger<HomeController> to the controller. When the GetWelcome() method is called, the log message is sent to the console (or whichever logging provider is configured).

By default, in .NET 6/7/8 Web API, you’ll see something like this in your console:

info: MyApp.Controllers.HomeController[0]
      Welcome endpoint was called!

This output comes directly from the code:

_logger.LogInformation("Welcome endpoint was called!");

The key point here is that you never manually create the logger and you don’t have to configure how the logs are written—the DI container does it for you. Instead of the class being responsible for creating its own dependencies, it simply asks for them in the constructor. This is exactly what Dependency Injection is all about: your class declares what it needs, and the framework provides it automatically.

Types of Dependency Injection

1. Constructor Injection (Most Common)

We already followed constructor injection in example above. In this approach, dependencies are passed into a class through its constructor. It ensures that the dependency is available as soon as the object is created. This is the most widely used method in .NET Core and is also considered best practice.

2. Method Injection

Method Injection is when a class receives a dependency only when a specific method is called, instead of getting it through the constructor or a property. In other words, the dependency is passed as a parameter to the method that needs it. This is useful when the dependency is needed only occasionally or for certain actions. However, if multiple methods need the same dependency, you may end up passing it repeatedly, so it’s less commonly used than constructor injection.

3. Property Injection

Property Injection allows you to provide a class with its dependency through a public property instead of the constructor. In other words, you first create the object and then assign the dependency to its property. This can be useful when the dependency is optional or only needed later. However, you need to be careful—if the property is not set before using it, it can cause errors, which is why this approach is used less often than constructor injection.

Service Lifetimes in .NET Core

Service lifetimes in .NET Core determine how long a dependency object lives when it’s provided by the DI container. There are three main types:

  • Singleton: A single instance is created and shared across the entire application. For example, a configuration service that never changes can be registered as singleton.

  • Scoped: A new instance is created for each HTTP request. For example, a user session service can be scoped so each request gets its own instance.

  • Transient: A new instance is created every time the dependency is requested. For example, a lightweight helper service that performs simple calculations can be transient.

Choosing the right lifetime ensures efficient resource use, predictable behavior, and cleaner code while using dependency injection effectively.

builder.Services.AddSingleton<IEngine, PetrolEngine>();  // same instance for whole app
builder.Services.AddScoped<IEngine, PetrolEngine>();     // one per request
builder.Services.AddTransient<IEngine, PetrolEngine>();  // new every time
Benefits of Dependency Injection
  • Makes code cleaner and easier to maintain.

  • Reduces tight coupling between classes.

  • Allows you to swap implementations without changing your code.

  • Makes unit testing easier by enabling mock dependencies.

  • Leads to organized, reusable, and testable code.

  • Helps build scalable and flexible applications, especially in large projects.

Conclusion

Dependency Injection (DI) is a powerful design pattern in .NET Core that helps make your applications cleaner, more maintainable, and easier to test. By letting the framework provide the dependencies, you reduce tight coupling, improve flexibility, and can easily swap implementations without changing your classes. We saw how DI works with a simple Car and Engine example and also in a real-world scenario using ILogger<T>, along with the different service lifetimes—Singleton, Scoped, and Transient. Understanding and using DI effectively is a key step toward writing professional, robust, and scalable .NET Core applications.

What’s Next?

In the next article, we will dive into Microservices in .NET Core. You’ll learn how to design and implement small, independent services that work together to form a larger application. We’ll also go through a practical example to see how microservices communicate, how to manage dependencies, and how DI plays a role in building scalable and maintainable applications.

ASP.NET Core 9.0 Hosting Recommendation

One of the most important things when choosing a good ASP.NET Core 9.0 hosting is the feature and reliability. HostForLIFE is the leading provider of Windows hosting and affordable ASP.NET Core, their servers are optimized for PHP web applications. The performance and the uptime of the hosting service are excellent and the features of the web hosting plan are even greater than what many hosting providers ask you to pay for. 

At HostForLIFE.eu, customers can also experience fast ASP.NET Core hosting. The company invested a lot of money to ensure the best and fastest performance of the datacenters, servers, network and other facilities. Its datacenters are equipped with the top equipments like cooling system, fire detection, high speed Internet connection, and so on. That is why HostForLIFEASP.NET guarantees 99.9% uptime for ASP.NET Core. And the engineers do regular maintenance and monitoring works to assure its Orchard hosting are security and always up.

 

Read More...

Tuesday, 23 September 2025

Passkeys in ASP.NET Core

Leave a Comment

I've created this tutorial to help you implement passkeys in ASP.NET Core. It offers a complete, executable code example and goes over the fundamental ideas. The most recent ASP.NET Core updates, on which the entire system is based, provide native passkey support, which facilitates much easier integration.


This is a comprehensive tutorial on how to use passkeys in ASP.NET Core.

Implementing Passkeys in ASP.NET Core

1. Introduction: The Dawn of a Passwordless Future

In an increasingly security-conscious digital landscape, passwords have become a liability. They are susceptible to phishing, credential stuffing, and data breaches. Passkeys, built on the Web Authentication (WebAuthn) standard, offer a revolutionary alternative. They replace traditional passwords with a secure, phishing-resistant public-private key pair. In this guide, we will explore how to integrate this powerful authentication method into your ASP.NET Core application, leveraging the framework's built-in Identity features and the WebAuthn API.

2. Understanding the Core Concepts

Passkeys rely on a simple yet robust cryptographic model. This model involves three key components:

  • Relying Party (RP): This is your ASP.NET Core server. It is the entity that you, the developer, control, and it's the server the user is trying to authenticate with.

  • Authenticator: This is the user's device—a smartphone, laptop, or a physical security key (like a YubiKey). It securely holds the private key and performs the cryptographic signing.

  • WebAuthn API: A JavaScript API in the browser that acts as the bridge between the Relying Party and the Authenticator, orchestrating the registration and authentication ceremonies.

When a user registers a passkey, their authenticator generates a unique public-private key pair for your website (the Relying Party). The private key remains on the device, never leaving it. The public key is securely sent to your server, where it is stored and linked to the user's account. For subsequent logins, the server sends a unique, cryptographic challenge to the user's browser. The browser then prompts the authenticator to use the private key to sign the challenge. The signed challenge is sent back to the server, which verifies it using the stored public key. If the signature is valid, the user is authenticated.

This process is inherently phishing-resistant because the user never types a secret that can be stolen, and the cryptographic signature is bound to your specific website.

3. Server-Side Implementation with ASP.NET Core Identity

The introduction of native passkey support in ASP.NET Core Identity simplifies the server-side implementation significantly, eliminating the need for complex external libraries for most use cases.

Step 1. Project Setup

Begin by creating a new ASP.NET Core Web App with the "Individual Accounts" authentication type. This template automatically includes ASP.NET Core Identity and a database context.

Step 2. Configure Passkeys

In your Program.cs file, you need to enable passkey support within the Identity configuration. This is a simple, one-line addition.

// Add services to the container.
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddPasskeys() // New in .NET 10 or later
    .AddDefaultTokenProviders();

The AddPasskeys() extension method handles all the necessary service registrations and middleware for WebAuthn.

Step 3. Registration Endpoint

The registration process requires two parts: generating the creation options and verifying the attestation response.

A. Generate Creation Options: This API endpoint is responsible for creating a PublicKeyCredentialCreationOptions object, which is a JSON payload that the browser needs to create the passkey.

[HttpPost("login/finish")]
public async Task<IActionResult> FinishAuthentication([FromBody] JsonElement credential)
{
    var result = await _signInManager.SignInWithPasskeyAsync(credential.GetRawText());
    if (result.Succeeded)
    {
        return Ok("Login successful.");
    }
    return Unauthorized(result.ToString());
}

B. Verify Attestation Response: After the browser receives the options and the user creates the passkey, it sends the result (the attestation) back to your server. This endpoint verifies the attestation and saves the new credential.

Step 4. Authentication Endpoint

The login process is similar to registration, but it uses the navigator.credentials.get() method.

4. Client-Side Implementation (JavaScript)

The front-end code is responsible for calling the server endpoints and interacting with the WebAuthn API.

const bufferToBase64Url = (buffer) => {
    const bytes = new Uint8Array(buffer);
    let str = '';
    for (const char of bytes) {
        str += String.fromCharCode(char);
    }
    return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
};

const base64UrlToBuffer = (base64Url) => {
    const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
    const binary = atob(base64);
    const len = binary.length;
    const bytes = new Uint8Array(len);
    for (let i = 0; i < len; i++) {
        bytes[i] = binary.charCodeAt(i);
    }
    return bytes.buffer;
};

// Function to handle passkey registration
const registerPasskey = async (username) => {
    try {
        // Step 1: Get registration options from the server
        const response = await fetch('/Passkeys/register/start', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(username),
        });

        if (!response.ok) {
            throw new Error(`Server error: ${response.statusText}`);
        }

        const options = await response.json();

        // Convert base64Url-encoded fields to ArrayBuffers
        options.publicKey.challenge = base64UrlToBuffer(options.publicKey.challenge);
        options.publicKey.user.id = base64UrlToBuffer(options.publicKey.user.id);

        if (options.publicKey.excludeCredentials) {
            options.publicKey.excludeCredentials.forEach(cred => {
                cred.id = base64UrlToBuffer(cred.id);
            });
        }

        // Step 2: Call the WebAuthn API to create the credential
        const credential = await navigator.credentials.create(options);

        // Step 3: Serialize and send the credential to the server
        const attestationResponse = {
            id: credential.id,
            rawId: bufferToBase64Url(credential.rawId),
            type: credential.type,
            response: {
                attestationObject: bufferToBase64Url(credential.response.attestationObject),
                clientDataJSON: bufferToBase64Url(credential.response.clientDataJSON),
            },
        };

        const verificationResponse = await fetch('/Passkeys/register/finish', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(attestationResponse),
        });

        if (verificationResponse.ok) {
            console.log('Passkey registered successfully!');
        } else {
            const error = await verificationResponse.text();
            console.error(`Passkey registration failed: ${error}`);
        }

    } catch (error) {
        console.error('Registration failed:', error);
    }
};

// Function to handle passkey authentication
const authenticatePasskey = async (username) => {
    try {
        // Step 1: Get authentication options from the server
        const response = await fetch('/Passkeys/login/start', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(username),
        });

        if (!response.ok) {
            throw new Error(`Server error: ${response.statusText}`);
        }

        const options = await response.json();

        // Convert base64Url-encoded fields to ArrayBuffers
        options.publicKey.challenge = base64UrlToBuffer(options.publicKey.challenge);
        options.publicKey.allowCredentials.forEach(cred => {
            cred.id = base64UrlToBuffer(cred.id);
        });

        // Step 2: Call the WebAuthn API to get the assertion
        const credential = await navigator.credentials.get(options);

        // Step 3: Serialize and send the assertion to the server
        const assertionResponse = {
            id: credential.id,
            rawId: bufferToBase64Url(credential.rawId),
            type: credential.type,
            response: {
                authenticatorData: bufferToBase64Url(credential.response.authenticatorData),
                clientDataJSON: bufferToBase64Url(credential.response.clientDataJSON),
                signature: bufferToBase64Url(credential.response.signature),
                userHandle: bufferToBase64Url(credential.response.userHandle),
            },
        };

        const verificationResponse = await fetch('/Passkeys/login/finish', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(assertionResponse),
        });

        if (verificationResponse.ok) {
            console.log('Passkey login successful!');
        } else {
            const error = await verificationResponse.text();
            console.error(`Passkey login failed: ${error}`);
        }

    } catch (error) {
        console.error('Login failed:', error);
    }
};

// Example usage
// await registerPasskey('testuser');
// await authenticatePasskey('testuser');
```
Conclusion: A Simpler, More Secure Future

Implementing passkeys in ASP.NET Core has never been easier, especially with the framework's native support. By adopting this technology, you are not only providing a more secure login experience that is resistant to the most common attack vectors, but you are also dramatically improving user convenience. This passwordless flow removes the cognitive burden of remembering complex passwords and the friction of forgotten password resets. It's a powerful step toward a more secure, user-friendly web.

This guide provides a solid foundation for your project. You can now build upon this knowledge by adding features like multi-factor authentication, account management pages to view and revoke passkeys, and user interface elements that provide clear feedback during the registration and authentication processes. Let me know if you would like to dive deeper into the fido2-net-lib library for more advanced configurations or explore how to integrate passkeys with existing identity providers.

Best ASP.NET Core 10.0 Hosting Recommendation

One of the most important things when choosing a good ASP.NET Core 8.0 hosting is the feature and reliability. HostForLIFE is the leading provider of Windows hosting and affordable ASP.NET Core, their servers are optimized for PHP web applications. The performance and the uptime of the hosting service are excellent and the features of the web hosting plan are even greater than what many hosting providers ask you to pay for. 

At HostForLIFEASP.NET, customers can also experience fast ASP.NET Core hosting. The company invested a lot of money to ensure the best and fastest performance of the datacenters, servers, network and other facilities. Its datacenters are equipped with the top equipments like cooling system, fire detection, high speed Internet connection, and so on. That is why HostForLIFEASP.NET guarantees 99.9% uptime for ASP.NET Core. And the engineers do regular maintenance and monitoring works to assure its Orchard hosting are security and always up.
Read More...

Thursday, 18 September 2025

In ASP.NET Core, what is launchsetting.json?

Leave a Comment

LaunchSettings.json is one of the first files you will see in the Properties folder when you start an ASP.NET Core project. This file is crucial for specifying how your application will function while it is being developed. To put it simply, it includes the configurations needed to run and debug your application using Visual Studio, Visual Studio Code, or the.NET CLI.


Consider it a blueprint that instructs your system on how to launch the project, including which port to use, which URL to use, and which environment (such as Development, Staging, or Production) to load.

What is LaunchSettings.json?

The launchSettings.json file is a JSON-based configuration file that:

  • Defines how your ASP.NET Core app is launched during development.

  • Configures profiles that represent different ways of running the app.

  • Specifies environment variables and command-line arguments.

For example, when you press F5 in Visual Studio, the IDE checks this file to know how to run your app.

Structure of LaunchSettings.json

Here’s a simple example of what a launchSettings.json file looks like:

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:5000",
      "sslPort": 44301
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "MyApp": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}
Key Components of LaunchSettings.json

IIS Settings

  • Found under iisSettings.

  • Defines how the app runs under IIS Express.

  • Example: URLs and ports used when running in IIS Express.

Example:

"iisSettings": {
  "iisExpress": {
    "applicationUrl": "http://localhost:5000",
    "sslPort": 44301
  }
}

Profiles

  • Each profile defines a way of launching your app.

  • Common profile types:

    • IIS Express (used when running via Visual Studio with IIS).

    • Project (runs directly with dotnet run).

Example:

"profiles": {
  "MyApp": {
    "commandName": "Project",
    "applicationUrl": "https://localhost:5001;http://localhost:5000",
    "environmentVariables": {
      "ASPNETCORE_ENVIRONMENT": "Development"
    }
  }
}
Environment Variables
  • Define the environment your app runs in.

  • Example values: Development, Staging, Production.

  • Helps control configuration like logging levels, connection strings, and debugging features.

Example:

"environmentVariables": {
  "ASPNETCORE_ENVIRONMENT": "Development"
}
Application URL
  • Specifies the HTTP/HTTPS URLs your app will run on.

  • You can run your app on multiple ports for both HTTP and HTTPS.

Example:

Launch Browser
  • If true, the browser will automatically open the app when you run it.

Example:

"launchBrowser": true
How Does LaunchSettings.json Work?
  • When you run the app, Visual Studio or dotnet run reads the active profile from launchSettings.json.

  • It sets the environment variables and runs the app with the given URLs.

  • Based on the ASPNETCORE_ENVIRONMENT value, the app loads the correct appsettings.{Environment}.json file.

Example:
If ASPNETCORE_ENVIRONMENT is set to Development, then the app will load:

appsettings.Development.json
Why is LaunchSettings.json Important?
  • Makes development setup easier.

  • Provides flexibility for different environments (Dev, Test, Staging).

  • Helps teams work on the same project with consistent settings.

  • Reduces manual setup every time you run the project.

Summary

The launchSettings.json file in ASP.NET Core is a configuration file that controls how your application runs during development. It defines profiles, URLs, ports, and environment variables. By using it, developers can easily switch between environments, control how their app starts, and ensure consistency across different team members’ setups. Understanding launchSettings.json is key to managing and debugging ASP.NET Core applications effectively.

Read More...