1. Overview & Approach
Security testing for ASP.NET Core should include multiple layers:
Static Analysis (SAST): scan code for insecure patterns.
Dependency/Package Scanning: find vulnerable NuGet packages.
Configuration Review: Ensure framework and middleware settings are secure.
Dynamic Testing (DAST): exercise the running app to find runtime flaws.
Automated Integration Tests: programmatic tests that assert security properties (headers, auth, CSRF, cookie flags).
Manual Pen-testing: targeted manual checks for XSS, SQLi, auth/authorization bypass.
CI/CD enforcement: run security checks automatically.
This article focuses on C# code for configuration checks, middleware to enforce secure practices, and integration tests that can be added to CI.
2. Secure Configuration (Program.cs / Startup.cs)
Make sure your app has these basics in Program.cs (or Startup.cs for older templates):
// Program.cs (.NET 6 or later)
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddAuthentication("Cookies")
.AddCookie("Cookies", options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Lax; // or Strict where feasible
options.LoginPath = "/Account/Login";
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
});
// Add Antiforgery
builder.Services.AddAntiforgery(options =>
{
options.HeaderName = "X-CSRF-TOKEN";
options.Cookie.Name = "XSRF-TOKEN";
options.Cookie.HttpOnly = false; // client JS may need token
});
var app = builder.Build();
// Enforce HTTPS and HSTS
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts(); // Strict-Transport-Security header
}
app.UseHttpsRedirection();
// Security headers middleware (see next section)
app.UseMiddleware<SecurityHeadersMiddleware>();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapRazorPages();
app.Run();
Notes
UseAuthentication()must come beforeUseAuthorization().Keep secrets out of
appsettings.jsonin production; use environment variables or Azure Key Vault / AWS Secrets Manager.
3. Security Headers Middleware (C# ready)
Add a small middleware to assert security headers and add defaults. Drop this into your project:
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
public class SecurityHeadersMiddleware
{
private readonly RequestDelegate _next;
public SecurityHeadersMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
{
var headers = context.Response.Headers;
// Prevent clickjacking
if (!headers.ContainsKey("X-Frame-Options"))
headers["X-Frame-Options"] = "DENY";
// Prevent MIME-sniffing
if (!headers.ContainsKey("X-Content-Type-Options"))
headers["X-Content-Type-Options"] = "nosniff";
// Basic CSP — adapt for your app (don't use 'unsafe-inline' in production)
if (!headers.ContainsKey("Content-Security-Policy"))
headers["Content-Security-Policy"] = "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none'; frame-ancestors 'none';";
// Referrer policy
if (!headers.ContainsKey("Referrer-Policy"))
headers["Referrer-Policy"] = "no-referrer";
// Feature-Policy / Permissions-Policy (modern replacement)
if (!headers.ContainsKey("Permissions-Policy"))
headers["Permissions-Policy"] = "geolocation=(), microphone=()";
await _next(context);
}
}
Register it in Program.cs as shown earlier.
4. Input Validation & Model Binding
Prefer strongly typed models and validation attributes. Never trust user input.
public class CreateUserDto
{
[Required]
[StringLength(100, MinimumLength = 3)]
public string Username { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
// Avoid binding secret fields directly from the client if possible
[Required]
[MinLength(8)]
public string Password { get; set; }
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult CreateUser([FromForm] CreateUserDto dto)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
// proceed (hash password with a secure algorithm e.g. PBKDF2/Argon2/BCrypt)
return Ok();
}
Server-side rules
Use parameterized queries or EF Core LINQ. Do not concatenate SQL strings.
For EF Core raw SQL, use parameters:
dbContext.Database.ExecuteSqlRaw("UPDATE ... WHERE Id = {0}", id);
5. File Upload Security (example)
Validate file type, size, and store outside web root (or in blob storage). Example:
[HttpPost]
[RequestSizeLimit(5 * 1024 * 1024)] // 5 MB
public async Task<IActionResult> Upload(IFormFile file)
{
if (file == null || file.Length == 0) return BadRequest("Empty file.");
// Basic extension check
var permittedExtensions = new[] { ".jpg", ".png", ".pdf" };
var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
return BadRequest("Invalid file type.");
// Optionally validate content-type and check file signature bytes
// Save to safe location
var filePath = Path.Combine("/var/uploads", $"{Guid.NewGuid()}{ext}");
using (var stream = System.IO.File.Create(filePath))
{
await file.CopyToAsync(stream);
}
return Ok();
}
Recommendation: run antivirus/clamAV scanning on uploaded files and do content sniffing to prevent disguised executables.
6. Anti-Forgery (CSRF)
For web forms, use @Html.AntiForgeryToken() in Razor views and decorate POST action with [ValidateAntiForgeryToken]. For single-page apps, send the antiforgery token via a header (X-CSRF-TOKEN) and validate on the server (see AddAntiforgery in Program.cs).
7. Programmatic Security Tests (Integration Tests with C#)
Use Microsoft.AspNetCore.Mvc.Testing and xUnit to write integration tests that assert security headers, cookie flags, and auth/authorization behavior.
Install:
dotnet add package Microsoft.AspNetCore.Mvc.Testing
dotnet add package xunit
dotnet add package FluentAssertions
Example test project:
Join the conversation! Your thoughts help the community grow.