From c1271242349367bd45ff7c08a0097ce592efcf86 Mon Sep 17 00:00:00 2001 From: Mukund Raghav Sharma Date: Mon, 23 Feb 2026 06:52:28 -0800 Subject: [PATCH 1/2] Add securing-aspnetcore-apis skill (+7.0% eval, near-miss) Teaches ASP.NET Core API security: JWT bearer auth with proper TokenValidationParameters, CORS configuration (avoiding AllowAnyOrigin), critical middleware ordering, and rate limiting setup. Eval results: +7.0% improvement (threshold: 10%, near-miss) Includes eval.yaml with security setup scenario + negative test. --- .../skills/securing-aspnetcore-apis/SKILL.md | 204 ++++++++++++++++++ .../tests/securing-aspnetcore-apis/eval.yaml | 47 ++++ 2 files changed, 251 insertions(+) create mode 100644 src/dotnet/skills/securing-aspnetcore-apis/SKILL.md create mode 100644 src/dotnet/tests/securing-aspnetcore-apis/eval.yaml diff --git a/src/dotnet/skills/securing-aspnetcore-apis/SKILL.md b/src/dotnet/skills/securing-aspnetcore-apis/SKILL.md new file mode 100644 index 0000000000..2a5ee0e4de --- /dev/null +++ b/src/dotnet/skills/securing-aspnetcore-apis/SKILL.md @@ -0,0 +1,204 @@ +--- +name: securing-aspnetcore-apis +description: Secure ASP.NET Core APIs with authentication, authorization, JWT bearer tokens, CORS configuration, and rate limiting. Use when adding security to web APIs, configuring auth middleware, or fixing common security misconfigurations. +--- + +# Securing ASP.NET Core APIs + +## When to Use + +- Adding authentication/authorization to an ASP.NET Core API +- Configuring JWT bearer token validation +- Setting up CORS policies for browser clients +- Implementing rate limiting to prevent abuse +- Fixing security misconfigurations + +## When Not to Use + +- The user is building a server-rendered MVC app with cookie auth (different patterns) +- The app is internal-only behind a service mesh that handles auth +- The user needs OAuth provider setup (IdP-specific, not general .NET) + +## Inputs + +| Input | Required | Description | +|-------|----------|-------------| +| ASP.NET Core project | Yes | The API project to secure | +| Auth requirements | No | JWT, API key, OAuth, or mixed | + +## Workflow + +### Step 1: Add JWT Bearer authentication + +```bash +dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer +``` + +```csharp +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.Authority = "https://login.microsoftonline.com/{tenant-id}/v2.0"; + options.Audience = "api://{client-id}"; + + // CRITICAL: Do NOT disable these in production + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ClockSkew = TimeSpan.FromMinutes(5) // default; resist reducing to 0 + }; + }); + +builder.Services.AddAuthorization(); +``` + +### Step 2: Configure middleware in the CORRECT order + +**Middleware order is critical. Wrong order = auth bypassed silently.** + +```csharp +var app = builder.Build(); + +// 1. Exception handling first (catches errors from all middleware) +app.UseExceptionHandler("/error"); + +// 2. HTTPS redirection +app.UseHttpsRedirection(); + +// 3. CORS — MUST be before auth for preflight requests to work +app.UseCors(); + +// 4. Authentication — MUST be before Authorization +app.UseAuthentication(); + +// 5. Authorization — MUST be after Authentication +app.UseAuthorization(); + +// 6. Rate limiting — after auth so you can rate-limit per user +app.UseRateLimiter(); + +// 7. Endpoints +app.MapControllers(); +``` + +**Common mistake:** Putting `UseAuthorization()` before `UseAuthentication()` — auth checks run but identity is never set, so everything returns 401. + +### Step 3: Apply authorization policies + +**Per-endpoint (Minimal APIs):** +```csharp +app.MapGet("/api/orders", GetOrders) + .RequireAuthorization(); + +app.MapDelete("/api/orders/{id}", DeleteOrder) + .RequireAuthorization("AdminOnly"); +``` + +**Policy-based authorization:** +```csharp +builder.Services.AddAuthorization(options => +{ + options.AddPolicy("AdminOnly", policy => + policy.RequireRole("Admin")); + + options.AddPolicy("CanManageOrders", policy => + policy.RequireClaim("permission", "orders.write")); + + // Fallback policy — applies to ALL endpoints without explicit auth + options.FallbackPolicy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build(); +}); +``` + +**IMPORTANT:** Setting `FallbackPolicy` makes ALL endpoints require auth by default. Explicitly allow anonymous where needed: + +```csharp +app.MapGet("/health", () => "OK").AllowAnonymous(); +app.MapPost("/api/auth/login", Login).AllowAnonymous(); +``` + +### Step 4: Configure CORS correctly + +```csharp +builder.Services.AddCors(options => +{ + options.AddPolicy("Production", policy => + { + policy.WithOrigins( + "https://app.example.com", + "https://admin.example.com") + .WithMethods("GET", "POST", "PUT", "DELETE") + .WithHeaders("Authorization", "Content-Type") + .AllowCredentials(); // Required if frontend sends cookies/tokens + }); +}); + +// Apply globally +app.UseCors("Production"); +``` + +**NEVER do this in production:** +```csharp +// INSECURE — allows any origin to call your API +policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); +``` + +### Step 5: Add rate limiting (.NET 7+) + +```csharp +builder.Services.AddRateLimiter(options => +{ + // Global limiter + options.GlobalLimiter = PartitionedRateLimiter.Create( + context => RateLimitPartition.GetFixedWindowLimiter( + partitionKey: context.User?.Identity?.Name ?? context.Connection.RemoteIpAddress?.ToString() ?? "anonymous", + factory: _ => new FixedWindowRateLimiterOptions + { + PermitLimit = 100, + Window = TimeSpan.FromMinutes(1), + QueueLimit = 0 + })); + + options.RejectionStatusCode = StatusCodes.Status429TooManyRequests; +}); +``` + +### Step 6: Security headers + +```csharp +app.Use(async (context, next) => +{ + context.Response.Headers.Append("X-Content-Type-Options", "nosniff"); + context.Response.Headers.Append("X-Frame-Options", "DENY"); + context.Response.Headers.Append("X-XSS-Protection", "0"); // Modern browsers don't need it + context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin"); + context.Response.Headers.Append("Content-Security-Policy", "default-src 'self'"); + await next(); +}); +``` + +## Security Checklist + +- [ ] `UseAuthentication()` comes BEFORE `UseAuthorization()` +- [ ] JWT validation checks issuer, audience, lifetime, and signing key +- [ ] CORS `WithOrigins` lists specific origins (not `AllowAnyOrigin`) +- [ ] All endpoints require auth by default (FallbackPolicy) +- [ ] Health/login endpoints explicitly marked `AllowAnonymous` +- [ ] Rate limiting enabled with per-user partitioning +- [ ] HTTPS enforced with `UseHttpsRedirection` +- [ ] No secrets in `appsettings.json` (use user-secrets or Key Vault) + +## Common Pitfalls + +| Pitfall | Solution | +|---------|----------| +| Auth middleware order wrong | Auth → Authz, always in that order. CORS before both | +| `AllowAnyOrigin` in production | Whitelist specific origins | +| JWT secret in appsettings.json | Use environment variables, user-secrets, or Azure Key Vault | +| 401 instead of 403 | 401 = not authenticated; 403 = authenticated but not authorized. Check claims | +| CORS preflight failures | Browser sends OPTIONS; ensure CORS middleware handles it before auth | +| Rate limiter not per-user | Partition by user identity OR IP, not globally | diff --git a/src/dotnet/tests/securing-aspnetcore-apis/eval.yaml b/src/dotnet/tests/securing-aspnetcore-apis/eval.yaml new file mode 100644 index 0000000000..8965779361 --- /dev/null +++ b/src/dotnet/tests/securing-aspnetcore-apis/eval.yaml @@ -0,0 +1,47 @@ +scenarios: + - name: "Secure an ASP.NET Core API with JWT auth and CORS" + prompt: | + I'm building a public ASP.NET Core 8 Web API that will be consumed by a React SPA running on a different domain. I need to: + 1. Add JWT bearer authentication + 2. Configure CORS properly for the SPA + 3. Add rate limiting to prevent abuse + + My Program.cs currently has no auth: + + ```csharp + var builder = WebApplication.CreateBuilder(args); + builder.Services.AddControllers(); + var app = builder.Build(); + app.MapControllers(); + app.Run(); + ``` + + Show me the correct and secure way to set this up. + assertions: + - type: "output_matches" + pattern: "(AddAuthentication|JwtBearer|JwtBearerDefaults)" + - type: "output_matches" + pattern: "(AddCors|CORS|WithOrigins)" + - type: "output_matches" + pattern: "(UseAuthentication.*UseAuthorization|middleware.order|order.matters)" + rubric: + - "Added JWT bearer auth with proper TokenValidationParameters (ValidateIssuer, ValidateAudience, ValidateLifetime all true)" + - "Configured CORS with specific origins (NOT AllowAnyOrigin with credentials)" + - "Added rate limiting using AddRateLimiter with a fixed or sliding window policy" + - "Placed middleware in correct order: UseCors → UseAuthentication → UseAuthorization → UseRateLimiter" + - "Explicitly stated that middleware order matters and incorrect order silently bypasses security" + - "Did NOT disable token validation parameters or use AllowAnyOrigin in production config" + expect_tools: ["bash"] + timeout: 120 + + - name: "Security skill should not activate for internal service question" + prompt: "I have an internal gRPC service running in Kubernetes behind a service mesh. How do I set up communication between my two microservices?" + assertions: + - type: "output_not_contains" + value: "JwtBearer" + - type: "output_not_matches" + pattern: "(AddAuthentication|JWT|CORS|rate.limit)" + rubric: + - "Did NOT suggest JWT/CORS/rate limiting for an internal service mesh question" + - "Provided gRPC or service mesh relevant guidance" + timeout: 60 From 2fe1f48aa5c5fa1ae13dbe61bd4382323904e488 Mon Sep 17 00:00:00 2001 From: Mukund Raghav Sharma Date: Tue, 24 Feb 2026 17:21:32 -0800 Subject: [PATCH 2/2] Change to middleware ordering bug-fix scenario --- .../tests/securing-aspnetcore-apis/eval.yaml | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/src/dotnet/tests/securing-aspnetcore-apis/eval.yaml b/src/dotnet/tests/securing-aspnetcore-apis/eval.yaml index 8965779361..293fb8e4f5 100644 --- a/src/dotnet/tests/securing-aspnetcore-apis/eval.yaml +++ b/src/dotnet/tests/securing-aspnetcore-apis/eval.yaml @@ -1,36 +1,44 @@ scenarios: - - name: "Secure an ASP.NET Core API with JWT auth and CORS" + - name: "Fix CORS preflight 401 with JWT auth middleware ordering" prompt: | - I'm building a public ASP.NET Core 8 Web API that will be consumed by a React SPA running on a different domain. I need to: - 1. Add JWT bearer authentication - 2. Configure CORS properly for the SPA - 3. Add rate limiting to prevent abuse - - My Program.cs currently has no auth: + I set up JWT authentication and CORS on my ASP.NET Core 8 API but the React SPA on https://myapp.example.com keeps getting 401 errors on preflight OPTIONS requests. Here is my middleware pipeline: ```csharp - var builder = WebApplication.CreateBuilder(args); - builder.Services.AddControllers(); var app = builder.Build(); + app.UseHttpsRedirection(); + app.UseAuthentication(); + app.UseAuthorization(); + app.UseCors("AllowSPA"); + app.UseRateLimiter(); app.MapControllers(); app.Run(); ``` - Show me the correct and secure way to set this up. + And CORS is configured as: + ```csharp + builder.Services.AddCors(options => + { + options.AddPolicy("AllowSPA", b => b + .AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials()); + }); + ``` + + The JWT auth works fine when I test directly from Postman. What's wrong? assertions: - type: "output_matches" - pattern: "(AddAuthentication|JwtBearer|JwtBearerDefaults)" - - type: "output_matches" - pattern: "(AddCors|CORS|WithOrigins)" + pattern: "(UseCors.*before.*UseAuthentication|middleware.*order|order.*middleware|UseCors.*UseAuth)" - type: "output_matches" - pattern: "(UseAuthentication.*UseAuthorization|middleware.order|order.matters)" + pattern: "(WithOrigins|specific.*origin|AllowAnyOrigin.*AllowCredentials.*conflict|cannot.*AllowAnyOrigin.*AllowCredentials)" rubric: - - "Added JWT bearer auth with proper TokenValidationParameters (ValidateIssuer, ValidateAudience, ValidateLifetime all true)" - - "Configured CORS with specific origins (NOT AllowAnyOrigin with credentials)" - - "Added rate limiting using AddRateLimiter with a fixed or sliding window policy" - - "Placed middleware in correct order: UseCors → UseAuthentication → UseAuthorization → UseRateLimiter" - - "Explicitly stated that middleware order matters and incorrect order silently bypasses security" - - "Did NOT disable token validation parameters or use AllowAnyOrigin in production config" + - "Identified that UseCors must come BEFORE UseAuthentication so OPTIONS preflight requests are handled before auth rejects them" + - "Showed the correct middleware order: UseCors → UseAuthentication → UseAuthorization → UseRateLimiter" + - "Identified the AllowAnyOrigin + AllowCredentials conflict (CORS spec forbids this combination)" + - "Fixed CORS to use WithOrigins(\"https://myapp.example.com\") instead of AllowAnyOrigin when using AllowCredentials" + - "Explained why OPTIONS requests fail: browsers send preflight with no auth token, so auth middleware returns 401 before CORS handles it" + - "Provided the corrected full middleware pipeline code" expect_tools: ["bash"] timeout: 120