Distributed Systems with .Net

Distributed Systems with .Net

POST 20: JWT Authentication in .NET Aspire Microservices | Secure APIs with JwtBearer

Post 20 of 45 · Phase 4: Authentication and Security · 21 min read · No Azure subscription required · Paid early access

Distributed Systems with .Net's avatar
Distributed Systems with .Net
Aug 11, 2026
∙ Paid

OrderService has been open since Post 1. Any process with network access can POST to /orders/create. On a developer’s laptop, that is acceptable. In a staging environment, it is not — and in production, it is a critical security gap. Phase 4 closes it.

Post 20 adds JWT bearer authentication to OrderService. Unprotected POST to /orders/create → 401 Unauthorized. POST with a valid Authorization: Bearer {token} header → 200, message published to Service Bus. The token is issued by a new /auth/token test endpoint in UserService — a stand-in for the Azure AD token issuance that happens in production.

Two packages, one configuration change, two source files. The local development path uses a symmetric HMAC-SHA256 signing key in appsettings.Development.json. The production path replaces four lines with Microsoft.Identity.Web and an AzureAd configuration section — no code change, no new package, no subscription required to build and test it locally.

Career skill this post adds: JWT bearer authentication in ASP.NET Core 8 Minimal APIs — builder.Services.AddAuthentication().AddJwtBearer(), app.UseAuthentication(), .RequireAuthorization(). The OTel event hooks that propagate claim values to the Aspire Dashboard trace are the production debugging pattern that turns a 401 from “unknown failure” to “specific validation error in the span.” Appears on every Azure .NET senior role that lists API security alongside observability.

  • JWT structure — header, claims payload, signature — and what [Authorize] verifies

  • AddAuthentication().AddJwtBearer() — the three-line middleware registration

  • TokenValidationParameters — the six properties and what each does

  • Local symmetric key → production OIDC discovery — the one-section configuration swap

  • OTel event hooks on OnAuthenticationFailed and OnTokenValidated

  • .RequireAuthorization() vs .AllowAnonymous() — selective endpoint protection


System state: where we are

Phase 3 is complete. OrderService has Service Bus publishing, Event Hubs dual-streaming, Blob Storage writes, Table Storage config lookups, full App Insights telemetry, and zero authentication. Post 20 adds the security layer without changing any of the messaging or storage code.

Starting point: git checkout post/20-jwt-auth Run .\verify.ps1 — all 43 Phase 1–3 checks must pass. Important: verify.ps1 checks 17–29 now fetch a token from /auth/token before calling /orders/create. If /auth/token fails, those checks will fail. Confirm UserService is healthy before running the full suite.

User's avatar

Continue reading this post for free, courtesy of Distributed Systems with .Net.

Or purchase a paid subscription.
© 2026 Distributed Systems with .Net · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture