ASP.NET CORE Web API - Topic-wise Practice
10 production incidents. Each one took down Zomato. Fix them.
Topic 1: Model Binding & Overposting
{isGold:true, price:0.01}
Code:
public class Order {
public int Id {get;set;}
public decimal Price {get;set;}
public bool IsGold {get;set;}
public int UserId {get;set;}
}
[HttpPost]
public IActionResult Create(Order order) { // BINDING TO ENTITY
order.UserId = int.Parse(User.FindFirst("sub").Value); // Set server-side
_db.Orders.Add(order);
_db.SaveChanges();
return Created();
}
Question: Why did hacker get $0.01 Gold? Pick the fix and explain why others fail.
Topic 2: JWT Audience Validation
Code:
services.AddAuthentication("Bearer").AddJwtBearer(o => {
o.TokenValidationParameters = new() {
ValidateIssuer = true,
ValidIssuer = "https://auth.zomato.com",
ValidateAudience = false, // BUG
ValidateLifetime = true,
IssuerSigningKey = key
};
});
Question: Hacker has valid JWT from dev.zomato.com with aud: "dev". What happens on api.zomato.com and why?
Topic 3: CORS Credentials + Wildcard
Code:
builder.Services.AddCors(o => o.AddPolicy("Open", p => {
p.SetIsOriginAllowed(_ => true) // Reflect any origin
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials(); // DANGER
}));
Question: User logged into Zomato visits evil.com. What JS runs and what data leaks?
Topic 4: Middleware Order - Yellow Screen
Code:
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(e => e.MapControllers());
app.UseExceptionHandler("/error"); // TOO LATE
Question: Exception thrown in UseRouting. Why didn't UseExceptionHandler catch it? What's the fix?
Topic 5: CancellationToken & DB Meltdown
Code:
[HttpGet("restaurants")]
public async Task<IActionResult> Search([FromQuery] string q) {
return Ok(await _db.Restaurants
.Where(r => r.Name.Contains(q))
.ToListAsync()); // NO CANCELLATION TOKEN
}
Question: User closes app at 5s. Query takes 30s. What happens to DbConnection and why?
Topic 6: Captive Dependency in Singleton
Code:
builder.Services.AddSingleton<ICartService, CartService>();
builder.Services.AddScoped<AppDbContext, AppDbContext>();
public class CartService : ICartService {
private readonly AppDbContext _db; // CAPTURED FROM FIRST REQUEST
public CartService(AppDbContext db) => _db = db;
public async Task<Cart> GetCart(int userId) {
return await _db.Carts.FirstAsync(c => c.UserId == userId); // Returns wrong user
}
}
Question: Request #1: User 123. Request #2: User 456. Why does #2 get User 123's cart?
Topic 7: API Versioning - 400 on Missing Version
Code:
builder.Services.AddApiVersioning(opt => {
opt.DefaultApiVersion = new ApiVersion(1, 0);
opt.AssumeDefaultVersionWhenUnspecified = false; // BUG
});
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/restaurants")]
public class RestaurantsController : ControllerBase { }
Question: Client calls /api/restaurants with no version. What status and why? How to fix without breaking v2?
Topic 8: Rate Limiting Burst
Code:
opt.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext => {
var ip = httpContext.Connection.RemoteIpAddress?.ToString()?? "unknown";
return RateLimitPartition.GetFixedWindowLimiter(ip, _ => new() {
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
});
});
Question: Why did FixedWindow allow 200 req in 2 seconds? Which limiter fixes this?
Topic 9: Output Caching Data Leak
Code:
[HttpGet("profile")]
[OutputCache(Duration = 60)] // BUG - NO VaryByHeader
public async Task<UserProfile> GetProfile() {
var userId = User.FindFirst("sub").Value;
return await _db.Users.FindAsync(int.Parse(userId));
}
Question: User 1 calls /profile, cached. User 2 calls /profile. What does User 2 see and why?
Topic 10: async void Crash
Code:
public void Configure(IApplicationBuilder app) {
app.Use(async (ctx, next) => {
await LogRequestAsync(ctx); // async void inside
await next();
});
}
private async void LogRequestAsync(HttpContext ctx) { // BUG
await Task.Delay(100);
throw new Exception("DB down"); // UNOBSERVED EXCEPTION
}
Question: Why did the app crash instead of returning 500? What happens to async void exceptions in ASP.NET Core?
No comments yet. Be the first to share your thoughts!