diff --git a/src/AzLocal.Middleware/AuthStubMiddleware.cs b/src/AzLocal.Middleware/AuthStubMiddleware.cs
index 5101431..61f9d55 100644
--- a/src/AzLocal.Middleware/AuthStubMiddleware.cs
+++ b/src/AzLocal.Middleware/AuthStubMiddleware.cs
@@ -1,12 +1,32 @@
-using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http;
+using System.Security.Claims;
namespace AzLocal.Middleware;
+///
+/// Bypasses Azure AD authentication for local development.
+/// Injects a fake authenticated identity on every request so [Authorize] checks pass
+/// without needing a real Azure AD token or tenant.
+///
public class AuthStubMiddleware
{
private readonly RequestDelegate _next;
+ // Static so the same fake identity is reused on every request — no per-request allocation.
+ // authenticationType must be non-null/non-empty for IsAuthenticated to return true.
+ private static readonly ClaimsPrincipal FakeUser = new(new ClaimsIdentity(
+ [
+ new Claim("oid", "00000000-0000-0000-0000-000000000001"), // fake Azure object ID
+ new Claim("tid", "00000000-0000-0000-0000-000000000002"), // fake tenant ID
+ new Claim(ClaimTypes.Name, "azlocal-dev"),
+ ], authenticationType: "AzLocalStub"));
+
public AuthStubMiddleware(RequestDelegate next) => _next = next;
- public async Task InvokeAsync(HttpContext context) => await _next(context);
+ public async Task InvokeAsync(HttpContext context)
+ {
+ // Skip real token validation — stamp every request as authenticated locally.
+ context.User = FakeUser;
+ await _next(context);
+ }
}
diff --git a/src/AzLocal.Middleware/ImdsMiddleware.cs b/src/AzLocal.Middleware/ImdsMiddleware.cs
index de1670a..60a5d0e 100644
--- a/src/AzLocal.Middleware/ImdsMiddleware.cs
+++ b/src/AzLocal.Middleware/ImdsMiddleware.cs
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Http;
+using System.Text.Json;
namespace AzLocal.Middleware;
@@ -6,8 +7,27 @@ public class ImdsMiddleware
{
private readonly RequestDelegate _next;
+ private static readonly string FakeTokenJson = JsonSerializer.Serialize(new
+ {
+ access_token = "fake-azlocal-token",
+ expires_on = "99999999999",
+ resource = "https://management.azure.com/",
+ token_type = "Bearer"
+ });
+
public ImdsMiddleware(RequestDelegate next) => _next = next;
- public async Task InvokeAsync(HttpContext context) => await _next(context);
+ public async Task InvokeAsync(HttpContext context)
+ {
+ if (context.Request.Path.StartsWithSegments("/metadata/identity/oauth2/token"))
+ {
+ context.Response.ContentType = "application/json";
+ context.Response.StatusCode = 200;
+ await context.Response.WriteAsync(FakeTokenJson);
+ return; // Do NOT call _next — response is already written
+ }
+ await _next(context);
+ }
}
+
diff --git a/src/AzLocal.Middleware/RequestContextMiddleware.cs b/src/AzLocal.Middleware/RequestContextMiddleware.cs
index 6ae17cc..76a18f8 100644
--- a/src/AzLocal.Middleware/RequestContextMiddleware.cs
+++ b/src/AzLocal.Middleware/RequestContextMiddleware.cs
@@ -1,13 +1,17 @@
-using Microsoft.AspNetCore.Http;
+using AzLocal.Core.Routing;
+using Microsoft.AspNetCore.Http;
namespace AzLocal.Middleware;
public class RequestContextMiddleware
{
private readonly RequestDelegate _next;
-
public RequestContextMiddleware(RequestDelegate next) => _next = next;
- public async Task InvokeAsync(HttpContext context) => await _next(context);
+ public async Task InvokeAsync(HttpContext context)
+ {
+ context.Items["RequestContext"] = ServiceRouter.BuildContext(context.Request);
+ await _next(context);
+ }
}
diff --git a/src/AzLocal.Middleware/RequestLoggingMiddleware.cs b/src/AzLocal.Middleware/RequestLoggingMiddleware.cs
index 37c5ff0..3fb234c 100644
--- a/src/AzLocal.Middleware/RequestLoggingMiddleware.cs
+++ b/src/AzLocal.Middleware/RequestLoggingMiddleware.cs
@@ -1,13 +1,28 @@
using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using System.Diagnostics;
namespace AzLocal.Middleware;
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
+ private readonly ILogger _logger;
- public RequestLoggingMiddleware(RequestDelegate next) => _next = next;
+ public RequestLoggingMiddleware(RequestDelegate next, ILogger logger)
+ {
+ _next = next;
+ _logger = logger;
+ }
- public async Task InvokeAsync(HttpContext context) => await _next(context);
+ public async Task InvokeAsync(HttpContext context)
+ {
+ var sw = Stopwatch.StartNew();
+ _logger.LogDebug("→ {Method} {Path}", context.Request.Method, context.Request.Path);
+ await _next(context);
+ _logger.LogInformation("← {StatusCode} {Method} {Path} in {Elapsed}ms",
+ context.Response.StatusCode, context.Request.Method,
+ context.Request.Path, sw.ElapsedMilliseconds);
+ }
}