Secure Coding Guide — .
NET Code & SQL Security Confidential
SECURE CODING GUIDE
Code & SQL Security in .NET Applications
Best Practices, Patterns & Code Examples
1. Introduction
Security is not an optional feature — it is a fundamental requirement of every .NET application.
This guide covers the most critical security topics every .NET developer must understand: SQL
Injection prevention, input validation, authentication, authorization, data protection, and secure
coding patterns.
Why Security Matters
OWASP reports that Injection (including SQL Injection) and Broken Access Control are consistently
among the top web application vulnerabilities. A single overlooked query can expose an entire
database. Proper security practices protect your users, your data, and your business.
Threat Category Risk Level Section
SQL Injection CRITICAL Section 2
Authentication Flaws CRITICAL Section 4
Cross-Site Scripting HIGH Section 3
Insecure Data Storage HIGH Section 5
Broken Authorization HIGH Section 4
Sensitive Data Exposure MEDIUM Section 5
© 2025 — Secure .NET Development Page
Secure Coding Guide — .NET Code & SQL Security Confidential
2. SQL Security in .NET
2.1 SQL Injection — The #1 Threat
SQL Injection occurs when untrusted user input is directly embedded into SQL queries.
Attackers can manipulate queries to bypass authentication, read all data, modify records, or
even delete entire databases.
✘ VULNERABLE — string concatenation
// Direct string concatenation — NEVER do this!
string query = "SELECT * FROM Users WHERE Username = '" + username
+ "' AND Password = '" + password + "'";
// Attacker enters: admin'--
// Result: password check is completely bypassed!
✔ SAFE — parameterized query with typed parameters
using var conn = new SqlConnection(connectionString);
using var cmd = new SqlCommand(
"SELECT * FROM Users WHERE Username=@u AND Password=@p", conn);
[Link]("@u", [Link], 50).Value = username;
[Link]("@p", [Link], 100).Value = hashedPassword;
[Link]();
var reader = await [Link]();
// Input is treated as data — never executed as SQL code
2.2 Entity Framework Core — Safe Query Patterns
EF Core protects against SQL injection by default when using LINQ queries. Raw SQL methods
require extra care.
✔ EF Core — safe patterns
// LINQ — always safe (EF Core parameterizes automatically)
var user = await _context.Users
.Where(u => [Link] == username && [Link])
.FirstOrDefaultAsync();
// FromSqlInterpolated — safe (C# interpolation becomes SQL parameters)
var users = [Link]
.FromSqlInterpolated($"SELECT * FROM Users WHERE Name={name}");
// FromSqlRaw with AddWithValue — safe
var param = new SqlParameter("@Name", name);
var users = [Link]("SELECT * FROM Users WHERE Name=@Name",
param);
© 2025 — Secure .NET Development Page
Secure Coding Guide — .NET Code & SQL Security Confidential
✘ DANGEROUS — never use string interpolation in FromSqlRaw
// String interpolation in FromSqlRaw = INJECTION RISK!
var users = [Link](
$"SELECT * FROM Users WHERE Name='{name}'"); // DANGEROUS!
2.3 Stored Procedures & Least Privilege
Stored procedures provide an additional layer of protection. Combine them with a least-privilege
database account for defense in depth.
✔ Stored Procedure — safe & type-safe
-- SQL Server stored procedure (parameterized by design)
CREATE PROCEDURE sp_GetUserById
@UserId INT
AS BEGIN
SELECT Id, Username, Email FROM Users WHERE Id = @UserId;
END
-- C# call
using var cmd = new SqlCommand("sp_GetUserById", conn);
[Link] = [Link];
[Link]("@UserId", [Link]).Value = userId;
await [Link]();
Permission App DB User DBA
SELECT Yes Yes
INSERT Yes (limited) Yes
UPDATE Yes (limited) Yes
DELETE Rarely Yes
DROP / ALTER NEVER Yes
xp_cmdshell NEVER Disable
3. Input Validation & XSS Prevention
3.1 Server-Side Validation
Never trust data from users, APIs, or external systems. Always validate on the server side —
client-side validation is for UX only and can be bypassed.
✔ Data Annotations — server-side validation
public class UserRegistrationDto
© 2025 — Secure .NET Development Page
Secure Coding Guide — .NET Code & SQL Security Confidential
{
[Required]
[StringLength(50, MinimumLength = 3)]
[RegularExpression(@"^[a-zA-Z0-9_]+$")]
public string Username { get; set; }
[Required, EmailAddress]
public string Email { get; set; }
[Required]
[StringLength(100, MinimumLength = 12)]
public string Password { get; set; }
}
3.2 Prevent Cross-Site Scripting (XSS)
XSS occurs when malicious scripts are injected into web pages. Always encode output and use
Content Security Policy headers.
✘ XSS RISK — never render raw user input
@[Link]([Link]) // Executes embedded <script> tags!
✔ XSS Prevention — encode output + CSP header
// Razor auto-encodes by default — always safe
@[Link]
// Explicit encoding in C#
var safe = [Link](userInput);
// Add Content-Security-Policy header
[Link](async (ctx, next) => {
[Link]("Content-Security-Policy",
"default-src 'self'; script-src 'self'");
await next();
});
4. Authentication & Authorization
4.1 Password Hashing — Never Store Plain Text
Passwords must NEVER be stored in plain text or with weak algorithms like MD5 or SHA1.
These can be cracked in seconds with rainbow tables.
✘ NEVER — insecure password storage
// All of these are WRONG:
© 2025 — Secure .NET Development Page
Secure Coding Guide — .NET Code & SQL Security Confidential
[Link] = password; // plain text
[Link] = GetMd5Hash(password); // MD5 is broken
[Link] = SHA256(password); // too fast, no salt
✔ SAFE — [Link] Core Identity PasswordHasher (PBKDF2)
// [Link] Core Identity — PBKDF2 with salt (built-in)
var hasher = new PasswordHasher<User>();
// On registration
[Link] = [Link](user, plainTextPassword);
// On login — constant-time comparison
var result = [Link](
user, [Link], providedPassword);
if (result == [Link])
return Unauthorized(); // don't reveal the reason
Additional password hardening
• Rate-limit login attempts (5 failures = lockout) • Block known breached passwords via
HaveIBeenPwned API • Enforce minimum 12 characters, mixed case, numbers, symbols • Add Multi-
Factor Authentication (TOTP) — reduces takeover risk by 99%
4.2 Secure JWT Configuration
When using JWT tokens, validate all parameters and never store secrets in source code.
✔ Secure JWT validation — [Link]
// [Link]
[Link]
.AddAuthentication([Link])
.AddJwtBearer(options => {
[Link] = new() {
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true, // check expiry!
ValidateIssuerSigningKey = true,
ClockSkew = [Link](30),
ValidIssuer = config["Jwt:Issuer"],
ValidAudience = config["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
[Link](config["Jwt:Key"])) // from env!
};
});
[Link](); // must come BEFORE UseAuthorization
[Link]();
© 2025 — Secure .NET Development Page
Secure Coding Guide — .NET Code & SQL Security Confidential
JWT Setting Recommended Value
Algorithm HS256 (symmetric) or RS256 (asymmetric) — never
'none'
Access token expiry 15–60 minutes
Refresh token expiry 7–30 days, stored as HttpOnly cookie
Signing key length 32+ characters, stored in Key Vault
ClockSkew Set to 30 seconds (not default 5 minutes)
4.3 Policy-Based Authorization
Use [Authorize] attributes and define fine-grained policies rather than checking roles manually in
controller logic.
✔ Policy-based authorization + IDOR prevention
// [Link] — define policies
[Link](options => {
[Link]("AdminOnly",
policy => [Link]("Admin"));
[Link]("CanEditOrders",
policy => [Link]("permission", "[Link]"));
// Default policy — all endpoints require auth unless [AllowAnonymous]
[Link] = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser().Build();
});
// Controller
[Authorize(Policy = "AdminOnly")]
public IActionResult DeleteUser(int id) { ... }
[AllowAnonymous] // explicitly public
public IActionResult Login() { ... }
// IDOR prevention — always filter by authenticated user!
var userId = [Link]([Link])?.Value;
var order = [Link](
o => [Link] == id && [Link] == userId); // scope to user
5. Secrets Management & Data Protection
5.1 Never Hard-Code Secrets
Connection strings, API keys, JWT secrets, and other sensitive values must never be committed
to source control. Even after deletion, secrets remain in Git history.
© 2025 — Secure .NET Development Page
Secure Coding Guide — .NET Code & SQL Security Confidential
✘ NEVER — hard-coded secrets
// NEVER — secrets in code are visible to everyone with repo access
string connStr = "Server=prod-db;Password=MyPassword123!;";
string apiKey = "sk-abc123verysecretkey";
✔ SAFE — environment variables & Key Vault
// Development: dotnet user-secrets (stored outside project folder)
// $ dotnet user-secrets init
// $ dotnet user-secrets set "Jwt:Key" "your-32-char-secret+"
// Production: environment variables + Azure Key Vault
[Link]
.AddEnvironmentVariables()
.AddAzureKeyVault(vaultUri, new DefaultAzureCredential());
// Consume safely
var secret = [Link]["Jwt:Key"];
5.2 Encrypt Sensitive Data at Rest
Personally Identifiable Information (PII), payment data, and health records must be encrypted
before storage using [Link] Core Data Protection API.
✔ [Link] Core Data Protection API — AES-256 encryption
// Register — [Link]
[Link]()
.PersistKeysToAzureBlobStorage(blobClient)
.ProtectKeysWithAzureKeyVault(keyId, credential)
.SetDefaultKeyLifetime([Link](90));
// Service usage
public class UserService(IDataProtectionProvider provider)
{
private readonly IDataProtector _p =
[Link]("UserPII.v1");
public string EncryptSSN(string ssn) => _p.Protect(ssn);
public string DecryptSSN(string enc) => _p.Unprotect(enc);
}
5.3 HTTPS & Security Headers
✔ HTTPS + Security headers — [Link]
// [Link]
[Link](); // redirect HTTP → HTTPS
[Link](); // Strict-Transport-Security header
© 2025 — Secure .NET Development Page
Secure Coding Guide — .NET Code & SQL Security Confidential
[Link](async (context, next) => {
var h = [Link];
h["X-Content-Type-Options"] = "nosniff";
h["X-Frame-Options"] = "DENY";
h["X-XSS-Protection"] = "1; mode=block";
h["Referrer-Policy"] = "strict-origin-when-cross-origin";
h["Content-Security-Policy"] =
"default-src 'self'; script-src 'self'; frame-ancestors 'none';";
await next();
});
Header Purpose
HSTS Forces HTTPS for 1+ year — prevents SSL stripping
attacks
X-Frame-Options: DENY Blocks iframe embedding — prevents clickjacking
attacks
Content-Security-Policy Whitelists allowed scripts/resources — strongest
XSS defense
X-Content-Type-Options Prevents MIME-type sniffing attacks
Referrer-Policy Controls how much referrer info is sent in requests
6. Secure Error Handling & Logging
6.1 Never Expose Stack Traces
Detailed error messages reveal system internals — file paths, database schema, framework
versions — to attackers. Show generic messages to users and log details securely on the
server.
✘ DANGEROUS — exposes system internals
// NEVER return exception details to the client
catch (Exception ex)
{
return StatusCode(500, [Link]()); // exposes stack trace!
}
✔ SAFE — global exception handler with correlation ID
// [Link] — global exception handler
[Link](errApp => {
[Link](async ctx => {
var ex = [Link]<IExceptionHandlerFeature>()?.Error;
var reqId = [Link]?.Id ?? [Link];
// Log full detail server-side only
© 2025 — Secure .NET Development Page
Secure Coding Guide — .NET Code & SQL Security Confidential
[Link](ex, "Unhandled exception {RequestId}", reqId);
[Link] = 500;
[Link] = "application/json";
await [Link](new {
message = "An unexpected error occurred.",
reference = reqId // safe correlation ID for support
});
});
});
6.2 Secure Logging — Never Log Sensitive Data
Logs are often stored in plain text and accessible to many people. Never log passwords, credit
card numbers, SSNs, or tokens.
✘ NEVER — logging sensitive data
// NEVER log credentials, tokens, or PII
_logger.LogInformation("User {u} logged in with password {p}", user, password);
_logger.LogDebug("Payment card: {card}", creditCardNumber); // PCI violation!
✔ SAFE — log actions and IDs only
// Log actions and IDs — never the sensitive data itself
_logger.LogInformation("User {UserId} authenticated successfully.", [Link]);
_logger.LogWarning("Failed login attempt #{Count} for {Username}.",
failCount, username); // log the attempt count, never the password
_logger.LogError("Payment failed for order {OrderId}.", orderId);
// Never log: orderId + cardNumber + cvv together (PCI violation)
7. Recommended NuGet Packages
Package Purpose How to Install
[Link] Password hashing, user mgmt, Built-in
lockout, MFA
[Link] JWT token validation middleware dotnet add package
[Link]
[Link]-Next BCrypt password hashing (alt. to dotnet add package
PBKDF2)
[Link] Advanced testable server-side dotnet add package
validation
[Link] Structured logging with sinks dotnet add package
[Link] Security HTTP headers (CSP, dotnet add package
© 2025 — Secure .NET Development Page
Secure Coding Guide — .NET Code & SQL Security Confidential
Package Purpose How to Install
e HSTS, etc.)
[Link] Azure Key Vault integration dotnet add package
[Link]
[Link] (HtmlSanitizer) Safe HTML sanitization for rich dotnet add package
content
HaveIBeenPwnedValidator Block passwords from known dotnet add package
breach lists
8. Pre-Deployment Security Checklist
Use this checklist before deploying any .NET application to production:
# Security Check Method Status
01 SQL Injection Parameterized queries / [ ] Done
Prevention EF Core LINQ
02 Password Hashing PBKDF2 / BCrypt / [ ] Done
Argon2id
03 No Plain-Text Secrets Env vars / Azure Key [ ] Done
Vault
04 JWT Validation Validate issuer, [ ] Done
audience, expiry, key
05 HTTPS Enforced UseHttpsRedirection + [ ] Done
UseHsts
06 Input Validation Data Annotations + [ ] Done
FluentValidation
07 XSS Prevention Razor auto-encoding + [ ] Done
CSP header
08 Authorization [Authorize] globally + [ ] Done
IDOR checks
09 PII Encrypted at Rest Data Protection API / [ ] Done
Key Vault
10 Secure Error Handling Generic client messages [ ] Done
+ server log
11 Least Privilege DB No DROP/ALTER/admin [ ] Done
Account rights for app
12 Security Headers CSP, X-Frame-Options, [ ] Done
HSTS
13 MFA Enabled TOTP for admin [ ] Done
accounts at minimum
14 No Sensitive Logging PII / passwords [ ] Done
© 2025 — Secure .NET Development Page
Secure Coding Guide — .NET Code & SQL Security Confidential
# Security Check Method Status
excluded from all logs
Key Takeaway
Security is built in layers. No single measure is enough. Always validate input, parameterize queries,
hash passwords, enforce authorization, use HTTPS, protect secrets, and handle errors safely. Run
this checklist before every production deployment.
— End of Document —
© 2025 — Secure .NET Development Page