Introduction
What if a breach could be stopped at the very first line of code? In 2023, 61% of data leaks originated from insecure APIs, according to a recent IBM report. .NET 8 introduces a leaner minimal API model, but the simplicity of the endpoint does not excuse security shortcuts. This guide shows how to embed zero‑trust principles directly into a C# minimal API, using Azure API Management, OpenID Connect, and fine‑grained policy checks.
Understanding Zero‑Trust for APIs
Zero‑trust assumes that every request is hostile until proven otherwise. In practice, this means three things: strong identity verification, least‑privilege access, and continuous validation of each call. For an API, the model translates into mandatory token validation, scoped permissions, and runtime policies that can deny traffic based on risk signals such as IP reputation or device compliance.
Creating a Minimal API in .NET 8
Start with the default template and add the authentication and authorization services. The following snippet registers JWT bearer handling backed by Azure AD and defines a simple weather endpoint that requires the read:weather scope.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://login.microsoftonline.com/{tenant-id}/v2.0";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidAudience = "api://{api-client-id}",
ValidateLifetime = true
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ReadWeather", policy =>
policy.RequireClaim("scp", "read:weather"));
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/weather", (HttpContext ctx) =>
{
return Results.Ok(new[] { "Sunny", "Rainy", "Cloudy" });
}).RequireAuthorization("ReadWeather");
app.Run();Notice the explicit RequireAuthorization call; without it the endpoint would be publicly accessible, violating zero‑trust.
Protecting the API with Azure API Management
Azure API Management (APIM) acts as a gateway that enforces additional checks before traffic reaches your .NET service. After publishing the minimal API to Azure App Service, create an APIM instance and import the OpenAPI definition. In the inbound policy, add a JWT validation clause that mirrors the .NET configuration and a rate‑limit rule to curb abuse.
<inbound>
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" require-expiration-time="true" require-scheme="Bearer" >
<openid-config url="https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" />
<required-claims>
<claim name="aud" match="any">api://{api-client-id}</claim>
</required-claims>
</validate-jwt>
<rate-limit calls="100" renewal-period="60" />
<base-url>https://myapi.azurewebsites.net</base-url>
</inbound>The APIM layer adds a second line of defense: even if a token is forged, the gateway will reject the call before it reaches your .NET container.
Integrating OpenID Connect for Identity
OpenID Connect (OIDC) provides a standardized way to obtain user identity and access tokens. Register a client application in Azure AD, enable the authorization_code flow, and request the openid profile email scopes together with your API scopes. The resulting ID token can be inspected in the .NET middleware to enrich the user principal with custom claims such as department or security level.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = "https://login.microsoftonline.com/{tenant-id}/v2.0";
options.ClientId = "{client-id}";
options.ClientSecret = "{client-secret}";
options.ResponseType = "code";
options.Scope.Add("api://{api-client-id}/read:weather");
options.SaveTokens = true;
});When a user logs in through the web UI, the middleware automatically validates the ID token and populates HttpContext.User, allowing downstream policies to make context‑aware decisions.
Policy‑Based Access Control in Code
Beyond static scopes, you can enforce dynamic rules using custom authorization handlers. The example below denies access to the weather endpoint for users whose department claim is not "Engineering".
public class DepartmentRequirement : IAuthorizationRequirement
{
public string RequiredDepartment { get; }
public DepartmentRequirement(string department) => RequiredDepartment = department;
}
public class DepartmentHandler : AuthorizationHandler
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DepartmentRequirement requirement)
{
var deptClaim = context.User.FindFirst("department");
if (deptClaim != null && deptClaim.Value == requirement.RequiredDepartment)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
// Registration
builder.Services.AddSingleton();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("EngineeringOnly", policy =>
policy.Requirements.Add(new DepartmentRequirement("Engineering")));
});
// Apply to endpoint
app.MapGet("/weather", ...).RequireAuthorization("EngineeringOnly"); This approach demonstrates zero‑trust at the granular level: even a valid token is insufficient if the user’s business context does not match the policy.
Testing and Monitoring
Automated integration tests should cover token validation, scope enforcement, and custom policy outcomes. Use Microsoft.AspNetCore.Mvc.Testing to spin up an in‑memory host and send HTTP requests with crafted JWTs. Log every authorization decision to Azure Monitor; the logs can be visualized in Log Analytics to spot anomalous patterns such as repeated failed attempts from the same IP range.
Conclusion
Embedding zero‑trust into a .NET 8 minimal API is achievable without sacrificing the framework’s lightweight appeal. By combining built‑in JWT validation, Azure API Management’s gateway policies, OpenID Connect for robust identity, and custom policy handlers for context‑aware access, you create a defense‑in‑depth architecture that meets modern security standards. The key takeaway: treat every request as untrusted, validate it at multiple layers, and enforce the least privilege principle programmatically.
Sources
Microsoft Docs – .NET 8 Minimal APIs; Azure API Management policies; OpenID Connect Core 1.0 specification.
Author: Mahmut Sarıkaya — sarikayadev.com