Hardening a public ASP.NET Core API: rate limiting, CORS, HSTS, validation and headers

ASP.NET Core Interview Q&A November 13, 2026 csharp

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.

csharp
 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