Skip to main content

How to Prevent Data Breaches: Security Checklist for Devs

Learn how to prevent data breaches with a practical security checklist for software teams, including C# code examples. Secure your .NET apps today.

Every week brings another headline about millions of leaked records, and the root cause is rarely an elite hacker. Most incidents trace back to ordinary engineering mistakes: a hard-coded connection string, an endpoint that forgot to check authorization, a password stored with MD5. If you want to know how to prevent data breaches, the answer is not a single product but a discipline your software team applies on every feature. This guide is a practical security checklist for software teams, with runnable C# and .NET examples, explaining not just what to do but why each control matters.

Why Data Breaches Happen: The Developer's View

Industry reports such as the Verizon DBIR consistently show the same top causes: stolen or weak credentials, web application vulnerabilities, misconfiguration, and human error. Notice that three of those four are firmly inside the software team's control. Data breach prevention for developers therefore comes down to a handful of recurring themes:

  • Secrets exposure — API keys and connection strings committed to source control.
  • Broken access control — the #1 item on the OWASP Top 10, where users can read data that isn't theirs.
  • Injection — SQL, command, and LDAP injection from unsanitized input.
  • Weak cryptography — plaintext passwords, reversible encryption, homemade algorithms.
  • Insufficient logging — breaches that go unnoticed for months because nobody was watching.

The checklist below maps directly to these causes. Treat it as a definition of done for any feature that touches user data.

How to Prevent Data Breaches: The 10-Point Security Checklist

1. Never Store Secrets in Source Code

Hard-coded secrets are the fastest route to a breach because repositories get cloned, forked, and leaked. In .NET, use the configuration system with User Secrets locally and a managed vault (Azure Key Vault, AWS Secrets Manager, HashiCorp Vault) in production.

// Program.cs — secrets come from environment/vault, never from appsettings.json in git
var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
    builder.Configuration.AddUserSecrets<Program>();
}
else
{
    var vaultUri = new Uri(builder.Configuration["KeyVault:Uri"]!);
    builder.Configuration.AddAzureKeyVault(vaultUri, new DefaultAzureCredential());
}

var connectionString = builder.Configuration.GetConnectionString("Default")
    ?? throw new InvalidOperationException("Connection string not configured.");

Why: Vaults give you rotation, audit logs, and per-environment isolation. Add a pre-commit secret scanner (gitleaks, GitHub secret scanning) so a slip never reaches the remote.

2. Parameterize Every Database Query

SQL injection is decades old and still appears in breach post-mortems. String concatenation is the bug; parameters are the fix.

// VULNERABLE — never do this
var sql = $"SELECT * FROM Users WHERE Email = '{email}'";

// SAFE — parameterized with Dapper
var user = await connection.QuerySingleOrDefaultAsync<User>(
    "SELECT * FROM Users WHERE Email = @Email",
    new { Email = email });

// SAFE — Entity Framework Core LINQ is parameterized automatically
var user2 = await db.Users.SingleOrDefaultAsync(u => u.Email == email);

// If you must use raw SQL in EF Core, use the interpolated overload
var users = await db.Users
    .FromSqlInterpolated($"SELECT * FROM Users WHERE Email = {email}")
    .ToListAsync();

Pitfall: FromSqlRaw with string interpolation is not safe. FromSqlInterpolated converts the interpolated values into parameters; FromSqlRaw($"...") does not.

3. Hash Passwords with a Slow, Salted Algorithm

If your database leaks, properly hashed passwords buy your users time. Use ASP.NET Core Identity's PasswordHasher, or Argon2id / bcrypt via a vetted library. Never use MD5, SHA-1, or unsalted SHA-256.

using Microsoft.AspNetCore.Identity;

public sealed class PasswordService
{
    private readonly PasswordHasher<string> _hasher = new();

    public string Hash(string userId, string password)
        => _hasher.HashPassword(userId, password); // PBKDF2, random salt, versioned format

    public bool Verify(string userId, string storedHash, string password)
    {
        var result = _hasher.VerifyHashedPassword(userId, storedHash, password);
        return result is PasswordVerificationResult.Success
                      or PasswordVerificationResult.SuccessRehashNeeded;
    }
}

Why: Slow hashes make brute-force attacks economically painful, and unique salts defeat rainbow tables. Pair this with multi-factor authentication for anything administrative.

4. Enforce Authorization on Every Endpoint (Not Just Authentication)

Authentication proves who the user is; authorization decides what they can touch. Insecure Direct Object Reference (IDOR) — changing /orders/123 to /orders/124 — is one of the most common ways customer data leaks.

[Authorize]
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    private readonly AppDbContext _db;
    public OrdersController(AppDbContext db) => _db = db;

    [HttpGet("{id:int}")]
    public async Task<IActionResult> Get(int id)
    {
        var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);

        // Scope the query to the caller — the ownership check is part of the query itself
        var order = await _db.Orders
            .Where(o => o.Id == id && o.CustomerId == userId)
            .SingleOrDefaultAsync();

        return order is null ? NotFound() : Ok(order);
    }
}

Make authorization the default globally so a forgotten attribute fails closed:

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

Why: Returning NotFound instead of Forbid also avoids confirming that the record exists to an attacker enumerating IDs.

5. Validate and Constrain All Input

Every byte from the network is hostile until proven otherwise. Use data annotations or FluentValidation, and reject rather than sanitize where possible.

public sealed record CreateUserRequest(
    [property: Required, EmailAddress, StringLength(254)] string Email,
    [property: Required, StringLength(100, MinimumLength = 2)] string DisplayName,
    [property: Range(13, 120)] int Age);

Also bind only the properties you intend to accept. Over-posting — where an attacker sends "IsAdmin": true and it maps straight onto your entity — is a classic privilege-escalation bug. Use dedicated request DTOs instead of binding directly to EF entities.

6. Encrypt Data in Transit and at Rest

Force HTTPS, enable HSTS, and encrypt sensitive columns so a stolen database backup is useless without the key.

// Program.cs
app.UseHsts();
app.UseHttpsRedirection();

// Field-level encryption with the Data Protection API
public sealed class SsnProtector
{
    private readonly IDataProtector _protector;

    public SsnProtector(IDataProtectionProvider provider)
        => _protector = provider.CreateProtector("Customer.SSN.v1");

    public string Protect(string ssn) => _protector.Protect(ssn);
    public string Unprotect(string cipher) => _protector.Unprotect(cipher);
}

Pitfall: In a multi-server deployment, persist Data Protection keys to shared storage (blob storage, Redis, SQL) and protect them with a vault key; otherwise every server generates its own keys and ciphertext becomes unreadable after a deploy.

7. Minimize the Data You Collect and Expose

You cannot leak what you never stored. Collect only what the feature needs, set retention periods, and never return full entities from an API. Project into response DTOs so password hashes, internal flags, and PII don't slip out by accident.

var profile = await _db.Users
    .Where(u => u.Id == userId)
    .Select(u => new ProfileResponse(u.DisplayName, u.AvatarUrl))
    .SingleAsync();

8. Keep Dependencies Patched

Many of the largest breaches (Equifax being the canonical example) exploited a known vulnerability in a library that was months out of date. Automate this:

  • Run dotnet list package --vulnerable --include-transitive in CI and fail the build on high-severity findings.
  • Enable Dependabot or Renovate for automatic update pull requests.
  • Pin the .NET SDK version in global.json and stay on a supported runtime.

9. Log Security Events — Without Logging Secrets

The average breach takes months to detect. Structured logging of authentication failures, permission denials, and unusual data access is what turns months into minutes.

_logger.LogWarning(
    "Authorization denied. UserId={UserId} Resource={Resource} Ip={Ip}",
    userId, resourceId, HttpContext.Connection.RemoteIpAddress);

Pitfall: Never log request bodies, tokens, or passwords. Redact PII at the logging-pipeline level so a compromised log store doesn't become a second breach.

10. Harden Configuration and Headers

Disable detailed error pages in production, add security headers, and rate-limit authentication endpoints to blunt credential-stuffing attacks.

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("login", o =>
    {
        o.PermitLimit = 5;
        o.Window = TimeSpan.FromMinutes(1);
    });
});

app.UseRateLimiter();

app.Use(async (context, next) =>
{
    context.Response.Headers["X-Content-Type-Options"] = "nosniff";
    context.Response.Headers["X-Frame-Options"] = "DENY";
    context.Response.Headers["Content-Security-Policy"] = "default-src 'self'";
    context.Response.Headers["Referrer-Policy"] = "no-referrer";
    await next();
});

app.MapPost("/login", LoginHandler).RequireRateLimiting("login");

Security Best Practices for the Whole Team

Code-level controls only stick if the process supports them. The teams that succeed at data breach prevention bake security into their workflow:

  • Threat model new features. Spend fifteen minutes asking "what could go wrong?" before writing code. It's cheaper than an incident response.
  • Automate security scanning in CI. Static analysis (Roslyn security analyzers, Semgrep, CodeQL), dependency scanning, and secret scanning should block merges.
  • Require security-focused code review. Add a checklist item: "Does this endpoint check ownership? Does this query use parameters?"
  • Apply least privilege everywhere. The application's database user should not be sa. Cloud roles should be scoped to exactly the resources needed.
  • Practice incident response. Know who to call, how to rotate every credential, and how to notify users before you need to. Regulations such as GDPR, the UK Data Protection Act, Australia's Notifiable Data Breaches scheme, and US state laws impose tight notification deadlines.
  • Train developers regularly. The OWASP Top 10 changes; so do attack techniques. A short quarterly session pays for itself.

Common Pitfalls That Undermine Data Breach Prevention

  • Trusting the client. Hidden form fields, disabled buttons, and JavaScript validation are UX features, not security controls. Re-validate everything server-side.
  • Rolling your own crypto. Use the framework's primitives. If you're calling Aes.Create() directly, make sure you understand IVs, authenticated modes (AES-GCM), and key management — or use Data Protection instead.
  • Security by obscurity. Unlisted URLs and GUID IDs are not authorization. Attackers find them.
  • Forgetting non-production environments. Staging databases seeded with real customer data and protected by admin/admin are a favorite entry point.
  • Verbose error responses. Stack traces and SQL errors returned to the browser tell attackers exactly what to try next. Use app.UseExceptionHandler() and generic problem-details responses in production.

Conclusion: Key Takeaways

Knowing how to prevent data breaches is less about buying tools and more about consistently applying a small set of proven controls. If your team does nothing else, do these:

  • Keep secrets out of source control and in a managed vault.
  • Parameterize every query and validate every input.
  • Hash passwords with a slow, salted algorithm and require MFA for privileged accounts.
  • Check authorization — ownership, not just login — on every endpoint, with a fail-closed default.
  • Encrypt data in transit and at rest, and collect only the data you actually need.
  • Patch dependencies automatically and fail CI on known vulnerabilities.
  • Log security events without logging secrets, and rehearse your incident response plan.

Print this security checklist for software teams, pin it next to your definition of done, and revisit it every sprint. A data breach costs millions in fines, remediation, and lost trust; the controls above cost a few hours of engineering discipline. That is the best trade in software.

About csharp-coder.com
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.

Comments

Popular posts from this blog

.NET MAUI Tutorial 2026: Build Cross-Platform Apps in C#

Learn .NET MAUI in 2026 to build iOS, Android, Windows & Mac apps from one C# codebase. Start this cross-platform tutorial with code examples today. .NET MAUI (Multi-platform App UI) is Microsoft's framework for building native iOS, Android, Windows, and macOS apps from a single C# codebase . If you've ever wanted to ship a mobile app without learning Swift, Kotlin, and Win32 separately, this .NET MAUI tutorial for 2026 is your starting point. In this guide you'll learn what .NET MAUI is, why it matters for cross-platform app development in C#, and how to build your first working app — with runnable code examples and the best practices senior engineers actually use in production. What Is .NET MAUI and Why Use It in 2026? .NET MAUI is the evolution of Xamarin.Forms, fully integrated into the modern .NET runtime. With one project and one language — C# — you target four platforms. The framework compiles to native UI controls on each device, so a button on iOS...

Angular 14 : 404 error during refresh page after deployment

In this article, We will learn how to solve 404 file or directory not found angular error in production.  Refresh browser angular 404 file or directory not found error You have built an Angular app and created a production build with ng build --prod You deploy it to a production server. Everything works fine until you refresh the page. The app throws The requested URL was not found on this server message (Status code 404 not found). It appears that angular routing not working on the production server when you refresh the page. The error appears on the following scenarios When you type the URL directly in the address bar. When you refresh the page The error appears on all the pages except the root page.   Reason for the requested URL was not found on this server error In a Multi-page web application, every time the application needs to display a page it has to send a request to the web server. You can do that by either typing the URL in the address bar, clicking on the Me...

Angular 14 CRUD Operation with Web API .Net 6.0

How to Perform CRUD Operation Using Angular 14 In this article, we will learn the angular crud (create, read, update, delete) tutorial with ASP.NET Core 6 web API. We will use the SQL Server database and responsive user interface for our Web app, we will use the Bootstrap 5. Let's start step by step. Step 1 - Create Database and Web API First we need to create Employee database in SQL Server and web API to communicate with database. so you can use my previous article CRUD operations in web API using net 6.0 to create web API step by step. As you can see, after creating all the required API and database, our API creation part is completed. Now we have to do the angular part like installing angular CLI, creating angular 14 project, command for building and running angular application...etc. Step 2 - Install Angular CLI Now we have to install angular CLI into our system. If you have already installed angular CLI into your system then skip this step.  To install angular CLI ope...