| name | security |
| description | Production security practices covering OWASP Top 10, input validation, SQL injection prevention, authentication/authorization, and secrets management. |
Security
Purpose: Production security practices to protect against common vulnerabilities.
Focus: Input validation, SQL injection prevention, authentication, secrets management.
OWASP Top 10 (2025)
- Broken Access Control - Authorization failures
- Cryptographic Failures - Weak encryption, exposed secrets
- Injection - SQL, NoSQL, command injection
- Insecure Design - Missing security controls
- Security Misconfiguration - Default configs, unnecessary features
- Vulnerable Components - Outdated dependencies
- Authentication Failures - Weak passwords, session management
- Software/Data Integrity - Unsigned updates, insecure CI/CD
- Logging/Monitoring Failures - Missing audit logs
- Server-Side Request Forgery (SSRF) - Unvalidated URLs
Input Validation
Always Validate & Sanitize
using FluentValidation;
public class UserInputValidator : AbstractValidator<UserInput>
{
public UserInputValidator()
{
RuleFor(x => x.Email)
.NotEmpty()
.EmailAddress()
.MaximumLength(255);
RuleFor(x => x.Username)
.NotEmpty()
.Length(3, 20)
.Matches(@"^[a-zA-Z0-9_]+$")
.WithMessage("Username can only contain letters, numbers, and underscores");
RuleFor(x => x.Age)
.InclusiveBetween(13, 120);
}
}
public async Task<IActionResult> CreateUser([FromBody] UserInput input)
{
var validator = new UserInputValidator();
var result = await validator.ValidateAsync(input);
if (!result.IsValid)
return BadRequest(result.Errors);
return Ok(await _service.CreateUserAsync(input));
}
Sanitize HTML
using Ganss.Xss;
public class HtmlSanitizer
{
private readonly HtmlSanitizer _sanitizer;
public HtmlSanitizer()
{
_sanitizer = new HtmlSanitizer();
_sanitizer.AllowedTags.Clear();
_sanitizer.AllowedTags.Add("p");
_sanitizer.AllowedTags.Add("br");
_sanitizer.AllowedTags.Add("strong");
_sanitizer.AllowedTags.Add("em");
}
public string Sanitize(string html) => _sanitizer.Sanitize(html);
}
SQL Injection Prevention
Use Parameterized Queries
public async Task<User> GetUser(string email)
{
var sql = $"SELECT * FROM Users WHERE Email = '{email}'";
return await _db.QueryFirstAsync<User>(sql);
}
public async Task<User> GetUser(string email)
{
var sql = "SELECT * FROM Users WHERE Email = @Email";
return await _db.QueryFirstAsync<User>(sql, new { Email = email });
}
public async Task<User> GetUser(string email)
{
return await _context.Users
.FirstOrDefaultAsync(u => u.Email == email);
}
Authentication & Authorization
JWT Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Secret"]))
};
});
app.UseAuthentication();
app.UseAuthorization();
Authorization Policies
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
options.AddPolicy("CanEditUser", policy =>
policy.RequireClaim("Permission", "EditUser"));
});
[Authorize(Policy = "AdminOnly")]
public IActionResult DeleteUser(int id)
{
_service.DeleteUser(id);
return NoContent();
}
Password Hashing
using BCrypt.Net;
public class PasswordService
{
public string HashPassword(string password)
{
return BCrypt.HashPassword(password, workFactor: 12);
}
public bool VerifyPassword(string password, string hash)
{
return BCrypt.Verify(password, hash);
}
}
user.Password = input.Password;
user.PasswordHash = _passwordService.HashPassword(input.Password);
Secrets Management
Use Environment Variables
{
"ConnectionStrings": {
"Default": "Server=localhost;Database=MyApp;User Id=myapp;"
}
}
var connectionString = builder.Configuration.GetConnectionString("Default");
var password = Environment.GetEnvironmentVariable("DB_PASSWORD");
connectionString += $"Password={password}";
Azure Key Vault
builder.Configuration.AddAzureKeyVault(
new Uri($"https://{keyVaultName}.vault.azure.net/"),
new DefaultAzureCredential());
var apiKey = builder.Configuration["ApiKey"];
User Secrets (Development Only)
dotnet user-secrets init
dotnet user-secrets set "ApiKey" "my-secret-key"
var apiKey = builder.Configuration["ApiKey"];
HTTPS & TLS
if (app.Environment.IsProduction())
{
app.UseHttpsRedirection();
app.UseHsts();
}
builder.Services.AddHsts(options =>
{
options.MaxAge = TimeSpan.FromDays(365);
options.IncludeSubDomains = true;
options.Preload = true;
});
CORS
builder.Services.AddCors(options =>
{
options.AddPolicy("Production", policy =>
{
policy.WithOrigins("https://myapp.com")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
app.UseCors("Production");
Rate Limiting
using AspNetCoreRateLimit;
builder.Services.AddMemoryCache();
builder.Services.Configure<IpRateLimitOptions>(options =>
{
options.GeneralRules = new List<RateLimitRule>
{
new RateLimitRule
{
Endpoint = "*",
Limit = 100,
Period = "1m"
},
new RateLimitRule
{
Endpoint = "*/api/login",
Limit = 5,
Period = "1m"
}
};
});
builder.Services.AddInMemoryRateLimiting();
app.UseIpRateLimiting();
Security Headers
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
context.Response.Headers.Add("X-Frame-Options", "DENY");
context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
context.Response.Headers.Add("Referrer-Policy", "no-referrer");
context.Response.Headers.Add("Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self'");
await next();
});
Best Practices
✅ DO
- Validate all inputs - Never trust user data
- Parameterize SQL - Use ORM or prepared statements
- Hash passwords - BCrypt with work factor ≥12
- Use HTTPS - Always in production
- Store secrets securely - Key Vault, env vars, never in code
- Implement RBAC - Role-based access control
- Enable rate limiting - Prevent abuse
- Add security headers - X-Content-Type-Options, CSP, etc.
- Log security events - Failed logins, access violations
- Update dependencies - Patch vulnerabilities regularly
- Use least privilege - Minimal necessary permissions
❌ DON'T
- Trust user input - Always validate/sanitize
- Concatenate SQL - Always use parameters
- Store plain passwords - Always hash
- Hardcode secrets - Use secure storage
- Allow any CORS origin - Whitelist specific domains
- Ignore security warnings - Fix them immediately
- Use default credentials - Change all defaults
- Skip authentication - Protect all sensitive endpoints
- Expose error details - Return generic errors to users
- Disable SSL validation - Never in production
Security Checklist
Resources
OWASP: Top 10 • Cheat Sheets
Tools: Snyk • OWASP ZAP • SonarQube
.NET Security: Microsoft Docs
See Also: 10-configuration.md • 03-error-handling.md
Last Updated: January 13, 2026