You can build the fastest, most scalable, and observable microservices in the world…
But if they’re not secure, they’re useless (or worse, dangerous).
In this final part of our 12-part .NET 8 Microservices Series, we’ll lock down our services with:
✅ Authentication & Authorization (AuthN vs AuthZ)
✅ JWT Bearer tokens with IdentityServer (Duende) or Azure AD (Entra ID)
✅ API Gateway enforcement (Ocelot/YARP)
✅ Service-to-service authentication (mTLS / Client Credentials)
✅ Secrets management & Zero-Trust best practices
1️⃣ Authentication vs Authorization
👉 Think of it like this:
-
Authentication (AuthN) → Who are you?
(Login with username/password, Google, or Azure AD) -
Authorization (AuthZ) → What are you allowed to do?
(Can this user access/orders? Do they have theadminrole?)
We’ll implement JWT (JSON Web Tokens) as access tokens issued by our Identity Provider.
2️⃣ Identity Provider Setup
You need a trusted Identity Provider (IdP) that issues secure tokens.
🔹 Option A: Self-hosted → Duende IdentityServer
Create a new project:
dotnet new web -n AuthServer
dotnet add package Duende.IdentityServer
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
Program.cs (minimal setup):
using Duende.IdentityServer;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddIdentityServer()
.AddInMemoryClients(new[]
{
new Client
{
ClientId = "orders-client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = { new Secret("supersecret".Sha256()) },
AllowedScopes = { "orders.read", "orders.write" }
}
})
.AddInMemoryApiScopes(new[]
{
new ApiScope("orders.read"),
new ApiScope("orders.write")
});
var app = builder.Build();
app.UseIdentityServer();
app.Run();
👉 Now request a token:
curl -X POST https://localhost:5005/connect/token \
-d "client_id=orders-client" \
-d "client_secret=supersecret" \
-d "grant_type=client_credentials" \
-d "scope=orders.read"
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}
🔹 Option B: Cloud → Azure AD (Entra ID)
- In Azure Portal → Register App → Expose API (
api://your-app-id/orders.read) - Create a client secret
- Install NuGet packages:
dotnet add package Microsoft.Identity.Web
dotnet add package Microsoft.Identity.Web.MicrosoftGraph
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
- Configure Program.cs:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("OrdersReader", policy =>
policy.RequireClaim("scp", "orders.read"));
});
appsettings.json:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "yourtenant.onmicrosoft.com",
"TenantId": "GUID",
"ClientId": "API-APP-ID"
}
3️⃣ Protecting APIs with JWT Validation
Example: Order Service (Program.cs):
using Microsoft.AspNetCore.Authentication.JwtBearer;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://localhost:5005"; // or Azure AD
options.Audience = "orders-api";
options.RequireHttpsMetadata = true;
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/orders", [Authorize(Policy = "OrdersReader")] () =>
{
return new[] { new { Id = 1, Item = "Laptop" } };
});
app.Run();
✅ Only requests with a valid JWT & orders.read scope succeed.
4️⃣ API Gateway Integration
The API Gateway should:
- Authenticate once at the edge
- Forward only valid requests downstream
Ocelot config example:
{
"Routes": [
{
"DownstreamPathTemplate": "/orders",
"UpstreamPathTemplate": "/api/orders",
"UpstreamHttpMethod": [ "GET" ],
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer",
"AllowedScopes": [ "orders.read" ]
}
}
]
}
Gateway Program.cs:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:5005"; // or Azure AD
options.Audience = "orders-api";
});
5️⃣ Service-to-Service Authentication
Even internal microservices must authenticate (Zero Trust).
🔹 Option A: Client Credentials Flow
- Order Service → requests token (
products.read) → calls Product Service withAuthorization: Bearer <token>.
🔹 Option B: mTLS (Mutual TLS)
- Each service has a certificate.
- Both client & server validate each other.
- Used for internal, high-security environments.
6️⃣ Secrets Management
❌ Don’t hardcode secrets in configs!
✅ Use secure vaults:
- Azure Key Vault
- AWS Secrets Manager
- HashiCorp Vault
In .NET 8:
builder.Configuration.AddAzureKeyVault(...);
7️⃣ Production Best Practices
- ✅ Validate Authority & Audience in JWT
- ✅ Use scopes / roles for fine-grained authorization
- ✅ Use short-lived tokens + refresh tokens
- ✅ Use mTLS or client credentials flow for inter-service auth
- ✅ Store secrets in vaults, not configs
- ✅ Enforce HTTPS everywhere
- ✅ API Gateway as policy enforcement point
- ✅ Monitor failed auth → detect intrusions
8️⃣ Putting It All Together (Flow)
- 🔑 Client logs in via Azure AD / IdentityServer → gets JWT
- 🛡 API Gateway validates token → forwards request
- 📦 Microservices validate locally (or trust gateway headers)
- 🔄 Internal calls use client credentials or mTLS
- 🔒 Secrets stored in vaults, TLS everywhere
🎯 Recap
In this final part, we secured our microservices with:
- IdentityServer or Azure AD for tokens
- JWT Bearer Authentication in APIs
- API Gateway enforcing scopes & policies
- Service-to-service auth (client creds / mTLS)
- Secrets vaults + Zero-Trust security
✨ With this, your 12-part .NET 8 Microservices Series is complete!
You now have:
👉 Fundamentals → REST → gRPC → Discovery → Health → Async → Design → Gateway → Event-driven → Deployment → Security
🔥 Congrats — you’ve just mastered end-to-end secure .NET 8 Microservices!
Comments
Post a Comment