
Secure Coding in .NET (C#): Best Practices, Checklist, and Automation
Writing secure code is no longer optional — it’s essential. Whether you’re building APIs, web apps, or enterprise tools in C#, following security best practices helps protect against common vulnerabilities and maintain trust with users.
In this post, you’ll learn:
- Key standards and guidelines to follow
- A real-world secure C# class example
- A ready-made secure code review checklist
- A GitHub Action to automatically scan your .NET code for security issues
1. Follow Industry Standards
a. Microsoft Secure Coding Guidelines
Microsoft provides official guidance for secure development:
Key recommendations:
- Validate all inputs
- Encode all outputs
- Apply the principle of least privilege
- Avoid hard-coded credentials
- Use secure frameworks
- Handle errors securely
b. OWASP Best Practices
The OWASP Top 10 is a must-follow industry standard for web application security.
Highlights include:
- A01: Broken Access Control
- A03: Injection (SQL, Command)
- A05: Security Misconfiguration
Also refer to the OWASP Cheat Sheet Series for deep dives into each topic.
2. Secure C# Class Example
Here is a practical example of secure coding using ASP.NET Core Web API:
[ApiController]
[Route(“api/[controller]”)]
public class AccountController : ControllerBase
{
private readonly ILogger
private readonly string _connectionString;
public AccountController(ILogger<AccountController> logger)
{
_logger = logger;
_connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING")
?? throw new InvalidOperationException("Connection string not found.");
}
[HttpPost("register")]
[AllowAnonymous]
public IActionResult Register([FromBody] RegisterRequest model)
{
if (!ModelState.IsValid)
return BadRequest("Invalid input.");
try
{
using var connection = new SqlConnection(_connectionString);
connection.Open();
using var cmd = new SqlCommand("INSERT INTO Users (Username, PasswordHash) VALUES (@Username, @PasswordHash)", connection);
cmd.Parameters.AddWithValue("@Username", model.Username);
cmd.Parameters.AddWithValue("@PasswordHash", HashPassword(model.Password));
cmd.ExecuteNonQuery();
return Ok(new { message = "User registered successfully." });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while registering user.");
return StatusCode(500, "An error occurred.");
}
}
private string HashPassword(string password)
{
using var sha256 = SHA256.Create();
var hashed = sha256.ComputeHash(Encoding.UTF8.GetBytes(password));
return Convert.ToBase64String(hashed);
}
}
public class RegisterRequest
{
[Required, StringLength(50, MinimumLength = 3)]
public string Username { get; set; }
[Required, StringLength(100, MinimumLength = 8)]
public string Password { get; set; }
}
Secure Practices Demonstrated:
- Parameterized SQL queries (avoiding SQL Injection)
- Input validation with data annotations
- Password hashing
- Environment variable for secrets
- Generic error response to users, detailed logs internally
3. Secure Code Review Checklist (Before Deployment)
Use this checklist to review your code before releasing it to production:
4. GitHub Action to Scan .NET Code Automatically
Add the following to .github/workflows/security-scan.yml
:
```yaml
name: .NET Security Scan
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Restore dependencies
run: dotnet restore
- name: Build project
run: dotnet build --no-restore
- name: Static code analysis
run: dotnet format analyze --severity error
- name: Scan for vulnerabilities
uses: dependency-check/Dependency-Check_Action@main
with:
project: "MyProject"
path: "."
format: "HTML"
out: "reports"
- name: Upload vulnerability report
uses: actions/upload-artifact@v4
with:
name: dependency-check-report
path: reports
- name: .NET built-in vulnerability check
run: dotnet list package --vulnerable
- name: Upload SARIF to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: reports/dependency-check-report.sarif
```
This action:
- Checks out your code
- Restores and builds the project
- Runs static code analysis
- Scans for known dependency vulnerabilities
- Uploads a security report to GitHub
Final Words
Security is a continuous process — not a one-time fix. By following best practices, reviewing code thoroughly, and automating checks, you can protect your .NET applications and build trust with your users.
Ready to level up your security? Add these practices to your next sprint or CI/CD pipeline today!
RELATED POSTS
View all