Sarıkaya Dev Logo

Zero‑Trust .NET 8 Microservices with Azure AD, mTLS, and OPA

Mahmut Sarıkaya 4 min read 2 Views 0
Zero‑Trust .NET 8 Microservices with Azure AD, mTLS, and OPA

Why Zero‑Trust matters for modern .NET 8 microservices

Every breach in the last two years involved a compromised service identity, not a stolen password. When a .NET 8 microservice architecture spans multiple clusters, the attack surface expands faster than traditional perimeter defenses. Applying a zero‑trust mindset—"never trust, always verify"—at the code level forces every request, internal or external, to be authenticated, encrypted, and authorized before processing.

Understanding zero‑trust in the .NET ecosystem

Zero‑trust is not a product; it is a set of patterns that align with the built‑in capabilities of .NET 8, such as minimal APIs, built‑in dependency injection, and the new Microsoft.AspNetCore.Authentication pipeline. The pattern consists of three pillars: identity (who you are), transport security (how you travel), and policy (what you can do). When each microservice enforces all three, the system becomes resilient to lateral movement.

Integrating Azure AD for identity across services

Azure Active Directory (Azure AD) provides a centralized identity provider that issues JWT access tokens. For a typical e‑commerce platform, a front‑end service might request a token for the "order" API, which is then validated by the downstream service. The following snippet configures Azure AD in a .NET 8 minimal API:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options => {
        options.Authority = \"https://login.microsoftonline.com/{tenant-id}/v2.0\";
        options.Audience = \"api://{client-id}\";
        options.TokenValidationParameters.ValidateLifetime = true;
    });

builder.Services.AddAuthorization();

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet(\"/orders\", [Microsoft.AspNetCore.Authorization.Authorize] (ClaimsPrincipal user) => {
    return Results.Ok($\"Hello {user.Identity.Name}, here are your orders.\");
});
app.Run();

Replace {tenant-id} and {client-id} with values from the Azure portal. The Authorize attribute guarantees that only callers with a valid Azure AD token can reach the endpoint.

Enforcing mutual TLS between microservices

Transport‑level trust is achieved with mutual TLS (mTLS). Unlike standard TLS, both client and server present certificates, eliminating impersonation even if a token is stolen. In Kubernetes, you can issue short‑lived X.509 certificates per deployment using tools like cert‑manager. The .NET 8 Kestrel server can be instructed to require client certificates as follows:

builder.WebHost.ConfigureKestrel(options => {
    options.ListenAnyIP(5001, listenOptions => {
        listenOptions.UseHttps(httpsOptions => {
            httpsOptions.ClientCertificateMode = Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode.RequireCertificate;
            httpsOptions.CheckCertificateRevocation = true;
        });
    });
});

When the downstream service receives a request, the certificate thumbprint can be extracted and mapped to a service identity stored in Azure AD or a custom registry. This double‑check—token validation plus certificate verification—creates a robust zero‑trust chain.

Policy‑driven authorization with Open Policy Agent

Static role‑based checks are insufficient when business rules evolve daily. Open Policy Agent (OPA) lets you write Rego policies that evaluate the request context, token claims, and even certificate attributes. Deploy OPA as a sidecar or a centralized policy service. A minimal Rego policy for a payment microservice might look like this:

package httpapi.authz

allow {
    input.method == \"POST\"
    input.path = [\"payments\"]
    input.user.role == \"finance\"
    input.tls.client_cert_valid == true
}

The .NET middleware calls OPA’s REST endpoint before the controller executes. If OPA returns {\"result\":true}, the request proceeds; otherwise a 403 is returned.

Putting it all together: sample startup configuration

The following example demonstrates how to wire Azure AD, mTLS, and OPA in a single Program.cs. Notice the sequential order: authentication, certificate check, then policy evaluation.

var builder = WebApplication.CreateBuilder(args);

// 1. Azure AD JWT validation
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(opts => {
        opts.Authority = \"https://login.microsoftonline.com/12345678-9abc-def0-1234-56789abcdef0/v2.0\";
        opts.Audience = \"api://my-microservice\";
    });

// 2. Require client certificates (mTLS)
builder.WebHost.ConfigureKestrel(k => {
    k.ListenAnyIP(5001, lo => {
        lo.UseHttps(ho => {
            ho.ClientCertificateMode = Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode.RequireCertificate;
        });
    });
});

// 3. OPA middleware (simple HttpClient wrapper)
builder.Services.AddHttpClient("opa", c => {
    c.BaseAddress = new Uri(\"http://opa:8181/v1/data/httpapi/authz\");
});

var app = builder.Build();
app.UseAuthentication();
app.Use(async (ctx, next) => {
    var client = ctx.RequestServices.GetRequiredService().CreateClient(\"opa\");
    var opaRequest = new {
        method = ctx.Request.Method,
        path = ctx.Request.Path.Value.Trim('/').Split('/'),
        user = new { role = ctx.User.FindFirst(\"role\").Value },
        tls = new { client_cert_valid = ctx.Connection.ClientCertificate != null }
    };
    var resp = await client.PostAsJsonAsync(string.Empty, opaRequest);
    var result = await resp.Content.ReadFromJsonAsync();
    if (result?.result != true) {
        ctx.Response.StatusCode = 403;
        await ctx.Response.WriteAsync(\"Policy denied\");
        return;
    }
    await next();
});
app.UseAuthorization();

app.MapGet(\"/secure-data\", [Microsoft.AspNetCore.Authorization.Authorize] () => "Sensitive information");
app.Run();

Deploy this service to a Kubernetes pod with a TLS secret containing the server certificate and the client CA bundle. The sidecar OPA container loads the Rego policy from a ConfigMap, enabling live policy updates without redeploying the .NET code.

Conclusion

Zero‑trust for .NET 8 microservices is achievable by layering Azure AD, mutual TLS, and OPA. Azure AD guarantees who the caller claims to be, mTLS confirms that the network path belongs to a trusted service, and OPA enforces fine‑grained business rules at runtime. By embedding these controls directly into the application pipeline, you eliminate the assumption that internal traffic is safe, dramatically reducing the risk of credential leakage and lateral movement.

Sources

- Microsoft Docs: Azure AD authentication for ASP.NET Core
- Open Policy Agent Documentation
- Kestrel HTTPS and client certificate configuration (Microsoft)

Author: Mahmut Sarıkaya — sarikayadev.com

Tags: #.NET 8 #microservices #zero-trust #Azure AD #mutual TLS
Share:
M

Written by

Mahmut Sarıkaya

Software Developer

Comments

No comments yet. Be the first to share your thoughts!

Leave a Comment

3 + 0 =