Why Zero‑Trust matters for modern .NET APIs
Data breaches have risen 67% since 2020, and the majority of incidents involve compromised API keys or weak token validation. In a Zero‑Trust model, every request is treated as hostile until proven otherwise, which forces developers to verify identity, context, and permissions on every call. For .NET 8 services that expose critical business functions, this shift from perimeter security to continuous verification is not optional—it’s a survival strategy.
Understanding Zero‑Trust for APIs
Zero‑Trust rests on three pillars: never trust, always verify, and assume breach. Applied to APIs, it translates into strict authentication, fine‑grained authorization, and real‑time token validation. Instead of relying on static secrets, you use short‑lived JWTs and introspect them at the resource server. The model also encourages micro‑segmentation: each API defines its own scope and audience, reducing the blast radius of a compromised token.
System requirements and initial setup
Before you start, ensure the development machine runs Windows 11 or a recent Linux distro, .NET 8 SDK (6.0.428 or later), and Docker for optional local IdentityServer containers. You will also need an Azure subscription with Azure Active Directory (AAD) tenant access.
Installation steps:
dotnet new webapi -n SecureApi --framework net8.0
cd SecureApi
dotnet add package Duende.IdentityServer
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
Configuring Duende IdentityServer in .NET 8
Duende IdentityServer acts as the primary token‑issuing authority. Define clients, API scopes, and resources in a static class. The following snippet shows a minimal configuration that supports the resource server and Azure AD as an external provider.
using Duende\IdentityServer.Models;
public static class Config
{
public static IEnumerable<Client> Clients => new[]
{
new Client
{
ClientId = "secure-api-client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = { new Secret("super-secret".Sha256()) },
AllowedScopes = { "api.read" }
}
};
public static IEnumerable<ApiScope> ApiScopes => new[]
{
new ApiScope("api.read", "Read access to Secure API")
};
public static IEnumerable<IdentityResource> IdentityResources => new[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
Wire the configuration into Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddIdentityServer()
.AddInMemoryClients(Config.Clients)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddDeveloperSigningCredential();
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:5001"; // IdentityServer URL
options.TokenValidationParameters.ValidateAudience = false;
});
var app = builder.Build();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Integrating Azure AD as an external identity provider
Azure AD can federate users into IdentityServer, enabling SSO for corporate accounts. Register a new app in Azure AD (App registrations → New registration). Capture the Application (client) ID, Directory (tenant) ID, and create a client secret. Then add the Azure AD configuration to appsettings.json:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "your-tenant-id",
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret"
}
}
Extend the IdentityServer pipeline to use the external provider:
builder.Services.AddAuthentication()
.AddOpenIdConnect("AzureAD", "Azure AD", options =>
{
options.Authority = $"{builder.Configuration[\"AzureAd:Instance\"]}{builder.Configuration[\"AzureAd:TenantId\"]}";
options.ClientId = builder.Configuration[\"AzureAd:ClientId\"];
options.ClientSecret = builder.Configuration[\"AzureAd:ClientSecret\"];
options.ResponseType = "code";
options.SaveTokens = true;
options.Scope.Add("api.read");
});
Now users authenticated via Azure AD receive a JWT that IdentityServer can introspect, preserving the Zero‑Trust guarantee that every token is validated against a trusted authority.
Implementing JWT token introspection
Instead of trusting the token signature locally, the resource server can call IdentityServer’s introspection endpoint. This approach is useful when you need revocation checks or when tokens are opaque. Add the introspection service:
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:5001";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = true
};
options.IntrospectionEndpoint = "https://localhost:5001/connect/introspect";
options.ClientId = "secure-api-client";
options.ClientSecret = "super-secret";
});
In a controller, protect an endpoint with the [Authorize] attribute and check the scope claim:
[ApiController]
[Route("api/[controller]")]
public class ValuesController : ControllerBase
{
[HttpGet]
[Authorize("api.read")]
public IActionResult Get()
{
return Ok(new { Message = "Zero‑Trust validated data" });
}
}
When a request arrives, the middleware forwards the token to the introspection endpoint, which returns active:true/false. A compromised token that has been revoked will be rejected instantly, fulfilling the “assume breach” principle.
Practical tips and common pitfalls
1. Keep token lifetimes short—5‑10 minutes for access tokens and 1‑2 hours for refresh tokens. This limits the window for abuse.
2. Store client secrets in Azure Key Vault or user‑secrets, never in source control.
3. Enable HTTP/2 on Kestrel for better performance when calling the introspection endpoint.
4. Test the flow with Postman: first request a client‑credentials token from /connect/token, then call the protected API with the Authorization: Bearer <token> header.
5. Monitor failed introspections; a sudden spike may indicate a token‑theft attack.
Conclusion
Zero‑Trust API security in .NET 8 is achievable by combining Duende IdentityServer, Azure AD federation, and JWT introspection. The architecture forces every request to be authenticated, authorized, and continuously verified, dramatically reducing the risk of token replay or credential leakage. By following the step‑by‑step configuration and the practical safeguards outlined above, developers can deliver APIs that meet modern security standards without sacrificing performance.
Sources
Microsoft Docs – Azure Active Directory authentication
Duende IdentityServer Documentation – Quickstarts for .NET 8
OWASP – Zero Trust Architecture guidance
Author: Mahmut Sarıkaya — sarikayadev.com