Hardening a public ASP.NET Core API: rate limiting, CORS, HSTS, validation and headers
Input validation at the boundary: DataAnnotations or FluentValidation, size limits (MaxRequestBodySize, string length, number of rows), allowlists for enums and for file types in uploads.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | // ====================================================================== // Q: How do you harden a public API? // // - Input validation at the boundary: DataAnnotations or FluentValidation, size // limits (MaxRequestBodySize, string length, number of rows), allowlists for // enums and for file types in uploads. // - Security headers: Content-Security-Policy, X-Content-Type-Options: nosniff, // Referrer-Policy. // - Errors: ProblemDetails without stack traces (Q6). Login responds identically // for "wrong user" and "wrong password" (user enumeration). // - Audit log for sensitive actions: who approved which order, who changed a // credit limit. // - Pipeline: SAST (CodeQL), secret scanning, dependency scanning (Q14), and // threat modeling (STRIDE) for new features. // The AI angle: code from AI agents often forgets [Authorize], tenant filters, // rate limiting and validation, and logs sensitive data. Those are the first // things I check in review (Q1, Q2). // ====================================================================== // Rate limiting, CORS, HSTS and middleware order builder.Services.AddRateLimiter(o => { o.RejectionStatusCode = StatusCodes.Status429TooManyRequests; o.AddPolicy("per-user", ctx => RateLimitPartition.GetFixedWindowLimiter( ctx.User.Identity?.Name ?? ctx.Connection.RemoteIpAddress?.ToString() ?? "anonymous", _ => new FixedWindowRateLimiterOptions { PermitLimit = 100, Window = TimeSpan.FromMinutes(1) })); }); builder.Services.AddCors(o => o.AddPolicy("shop", p => p .WithOrigins("https://shop.example.com") // never AllowAnyOrigin together with credentials .WithMethods("GET", "POST") .AllowAnyHeader() .AllowCredentials())); if (!app.Environment.IsDevelopment()) app.UseHsts(); app.UseHttpsRedirection(); app.UseCors("shop"); app.UseAuthentication(); app.UseAuthorization(); app.UseRateLimiter(); app.MapPost("/api/auth/login", LoginHandler).AllowAnonymous().RequireRateLimiting("per-user"); // brute force |