Mitigating API Vulnerabilities in .NET (C#)
ASP.NET Core provides robust, built-in mechanisms to defend against the OWASP API Security Top 10. Below are the key strategies and code examples for securing a .NET API.
1. Preventing BOLA / IDOR (API1:2023)
Never trust the ID provided in the URL. Always cross-reference it with the identity of the currently authenticated user.
Insecure:
[HttpGet("{orderId}")]
public async Task<IActionResult> GetOrder(int orderId)
{
// VULNERABLE: Any authenticated user can pass any orderId
var order = await _context.Orders.FindAsync(orderId);
return Ok(order);
}
Secure:
[HttpGet("{orderId}")]
public async Task<IActionResult> GetOrder(int orderId)
{
// Extract the User's ID from the JWT Claims
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
// SECURE: Filter the query to ensure the order belongs to this specific user
var order = await _context.Orders
.FirstOrDefaultAsync(o => o.Id == orderId && o.UserId == userId);
if (order == null) return NotFound();
return Ok(order);
}
2. Preventing Mass Assignment (API3:2023)
Never bind API request bodies directly to your Entity Framework database models. Attackers can inject fields like "IsAdmin": true.
Use Data Transfer Objects (DTOs):
// Only include fields the user is explicitly allowed to update
public class UpdateProfileDto
{
public string FirstName { get; set; }
public string LastName { get; set; }
// Note: IsAdmin or Balance is intentionally omitted
}
[HttpPut("profile")]
public async Task<IActionResult> UpdateProfile([FromBody] UpdateProfileDto dto)
{
var user = await _context.Users.FindAsync(CurrentUserId);
// Map only the safe fields from the DTO to the actual entity
user.FirstName = dto.FirstName;
user.LastName = dto.LastName;
await _context.SaveChangesAsync();
return Ok();
}
3. Rate Limiting (API4:2023)
Use the built-in ASP.NET Core Rate Limiting middleware (introduced in .NET 7).
// Program.cs
builder.Services.AddRateLimiter(options => {
options.AddFixedWindowLimiter("Basic", opt => {
opt.PermitLimit = 100;
opt.Window = TimeSpan.FromMinutes(1);
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 2;
});
});
var app = builder.Build();
app.UseRateLimiter(); // Apply middleware
// In Controller
[EnableRateLimiting("Basic")]
[HttpGet("data")]
public IActionResult GetData() { ... }
4. Preventing Security Misconfigurations (API8:2023)
CORS (Cross-Origin Resource Sharing)
Never use .AllowAnyOrigin() in production. Always specify exact domains.
builder.Services.AddCors(options =>
{
options.AddPolicy("StrictPolicy", policy =>
{
policy.WithOrigins("https://myfrontend.com")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
Hiding Stack Traces
Ensure detailed exceptions are only returned in the Development environment.