Security Fundamentals · How it works
3 min readRapid overview
- How it works
- OWASP Top 10 Security Risks (2021)
- 1. Broken Access Control (A01:2021)
- 2. Cryptographic Failures (A02:2021)
- 3. Injection (A03:2021)
- 4. Insecure Design (A04:2021)
- 5. Security Misconfiguration (A05:2021)
- 6. Vulnerable and Outdated Components (A06:2021)
- 7. Identification and Authentication Failures (A07:2021)
- 8. Software and Data Integrity Failures (A08:2021)
- 9. Security Logging and Monitoring Failures (A09:2021)
- 10. Server-Side Request Forgery (A10:2021)
- Secrets and Credentials Management
- Development Environment
- User Secrets (Recommended for Development)
- Environment Variables
- Local Configuration Files (gitignored)
- Production Environment
- Azure Key Vault (Recommended)
- AWS Secrets Manager
- HashiCorp Vault
- Connection String Security
- API Keys and External Service Credentials
- Input Validation and Sanitization
- Request Validation with FluentValidation
- Output Encoding
- Authentication and Authorization Best Practices
- JWT Token Security
- Policy-Based Authorization
- Security Checklist
How it works
OWASP Top 10 Security Risks (2021)
Understanding the OWASP Top 10 is essential for building secure applications. Here's how each applies to .NET development:
1. Broken Access Control (A01:2021)
- Risk: Users accessing resources or actions beyond their permissions
- Prevention:
- Use
[Authorize]attributes with policies and roles - Implement resource-based authorization for object-level access
- Deny by default; whitelist allowed actions
- Validate ownership before modifying resources
// ✅ Good: Resource-based authorization
[Authorize]
public async Task<IActionResult> EditDocument(int id)
{
var document = await _repository.GetAsync(id);
var authResult = await _authService.AuthorizeAsync(User, document, "EditPolicy");
if (!authResult.Succeeded)
return Forbid();
return View(document);
}
// ❌ Bad: Only checking authentication, not authorization
[Authorize]
public async Task<IActionResult> EditDocument(int id)
{
var document = await _repository.GetAsync(id);
return View(document); // Any authenticated user can edit any document!
}
2. Cryptographic Failures (A02:2021)
- Risk: Exposure of sensitive data due to weak or missing encryption
- Prevention:
- Use TLS 1.2+ for all data in transit
- Encrypt sensitive data at rest with AES-256
- Use strong password hashing (Argon2id, bcrypt, PBKDF2)
- Never store secrets in plaintext
// ✅ Good: Using ASP.NET Core Data Protection for encryption
public class SecureDataService
{
private readonly IDataProtector _protector;
public SecureDataService(IDataProtectionProvider provider)
{
_protector = provider.CreateProtector("SensitiveData.v1");
}
public string Protect(string plaintext) => _protector.Protect(plaintext);
public string Unprotect(string ciphertext) => _protector.Unprotect(ciphertext);
}
3. Injection (A03:2021)
- Risk: Untrusted data sent to interpreter as part of command/query
- Prevention:
- Use parameterized queries (EF Core, Dapper with parameters)
- Validate and sanitize all input
- Use ORMs that escape by default
// ✅ Good: Parameterized query
var user = await _context.Users
.Where(u => u.Email == email)
.FirstOrDefaultAsync();
// ✅ Good: Dapper with parameters
var user = await connection.QuerySingleOrDefaultAsync<User>(
"SELECT * FROM Users WHERE Email = @Email",
new { Email = email });
// ❌ Bad: String concatenation SQL injection vulnerability
var query = $"SELECT * FROM Users WHERE Email = '{email}'";
var user = await connection.QueryAsync(query);
4. Insecure Design (A04:2021)
- Risk: Missing or ineffective security controls in design
- Prevention:
- Threat modeling during design phase
- Security requirements in user stories
- Defense in depth (multiple layers)
- Principle of least privilege
5. Security Misconfiguration (A05:2021)
- Risk: Insecure default configs, open cloud storage, verbose errors
- Prevention:
- Disable detailed errors in production
- Remove default credentials
- Disable unnecessary features/services
- Use security headers
// ✅ Good: Proper environment-based configuration
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
// Security headers middleware
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", "strict-origin-when-cross-origin");
context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'");
await next();
});
6. Vulnerable and Outdated Components (A06:2021)
- Risk: Using components with known vulnerabilities
- Prevention:
- Regular dependency updates
- Use
dotnet list package --vulnerable - Implement automated security scanning in CI/CD
- Subscribe to security advisories
7. Identification and Authentication Failures (A07:2021)
- Risk: Weak authentication, session management flaws
- Prevention:
- Use ASP.NET Core Identity with secure defaults
- Implement MFA where possible
- Use secure session management
- Rate limit authentication attempts
// ✅ Good: Secure password requirements
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequiredLength = 12;
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.Lockout.MaxFailedAccessAttempts = 5;
});
8. Software and Data Integrity Failures (A08:2021)
- Risk: Code/infrastructure without integrity verification
- Prevention:
- Verify package signatures
- Use signed assemblies
- Implement CI/CD security controls
- Validate serialized data
9. Security Logging and Monitoring Failures (A09:2021)
- Risk: Insufficient logging to detect breaches
- Prevention:
- Log authentication events (success/failure)
- Log authorization failures
- Include correlation IDs
- Protect logs from tampering
// ✅ Good: Security event logging
public class SecurityAuditService
{
private readonly ILogger<SecurityAuditService> _logger;
public void LogAuthenticationAttempt(string username, bool success, string ipAddress)
{
_logger.LogInformation(
"Authentication {Result} for user {Username} from {IpAddress}",
success ? "succeeded" : "failed",
username,
ipAddress);
}
public void LogAuthorizationFailure(string userId, string resource, string action)
{
_logger.LogWarning(
"Authorization denied: User {UserId} attempted {Action} on {Resource}",
userId,
action,
resource);
}
}
10. Server-Side Request Forgery (A10:2021)
- Risk: Server fetches attacker-controlled URLs
- Prevention:
- Validate and sanitize URLs
- Use allowlists for external services
- Disable unnecessary URL schemes
- Network segmentation
// ✅ Good: URL validation with allowlist
public class SafeUrlFetcher
{
private static readonly HashSet<string> AllowedHosts = new()
{
"api.trusted-service.com",
"cdn.company.com"
};
public async Task<string> FetchAsync(string url)
{
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
throw new ArgumentException("Invalid URL");
if (uri.Scheme != "https")
throw new ArgumentException("Only HTTPS allowed");
if (!AllowedHosts.Contains(uri.Host))
throw new ArgumentException("Host not in allowlist");
return await _httpClient.GetStringAsync(uri);
}
}
Secrets and Credentials Management
Development Environment
Principles:
- Never commit secrets to source control
- Use different credentials for development
- Isolate development from production secrets
User Secrets (Recommended for Development)
# Initialize user secrets
dotnet user-secrets init
# Set a secret
dotnet user-secrets set "Database:ConnectionString" "Server=localhost;..."
dotnet user-secrets set "ExternalApi:ApiKey" "dev-key-12345"
// Automatically loaded in Development
var builder = WebApplication.CreateBuilder(args);
// User secrets are loaded when Environment is Development
var connectionString = builder.Configuration["Database:ConnectionString"];
Environment Variables
# Windows PowerShell
$env:Database__ConnectionString = "Server=localhost;..."
# Linux/macOS
export Database__ConnectionString="Server=localhost;..."
// Configuration automatically reads from environment variables
var connectionString = builder.Configuration["Database:ConnectionString"];
Local Configuration Files (gitignored)
// appsettings.Development.local.json (add to .gitignore)
{
"Database": {
"ConnectionString": "Server=localhost;Database=dev;..."
}
}
Production Environment
Principles:
- Use a dedicated secrets management service
- Rotate credentials regularly
- Audit access to secrets
- Use managed identities where possible (eliminates secrets entirely)
Azure Key Vault (Recommended)
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add Key Vault configuration
var keyVaultUri = builder.Configuration["KeyVault:Uri"];
builder.Configuration.AddAzureKeyVault(
new Uri(keyVaultUri),
new DefaultAzureCredential());
// Access secrets like regular configuration
var connectionString = builder.Configuration["Database-ConnectionString"];
// Using Managed Identity (no credentials needed!)
services.AddDbContext<AppDbContext>(options =>
{
var connectionString = configuration["Database-ConnectionString"];
options.UseSqlServer(connectionString);
});
AWS Secrets Manager
// Using AWS Secrets Manager
builder.Configuration.AddSecretsManager(configurator: options =>
{
options.SecretFilter = entry => entry.Name.StartsWith("MyApp/");
options.KeyGenerator = (_, name) => name.Replace("MyApp/", "").Replace("/", ":");
});
HashiCorp Vault
// Using HashiCorp Vault
builder.Configuration.AddVault(options =>
{
options.Address = "https://vault.company.com";
options.Token = Environment.GetEnvironmentVariable("VAULT_TOKEN");
options.SecretPath = "secret/data/myapp";
});
Connection String Security
// ✅ Good: Using Managed Identity (Azure SQL)
"Server=tcp:myserver.database.windows.net;Database=mydb;Authentication=Active Directory Managed Identity;"
// ✅ Good: Using Azure Key Vault reference in App Service
"@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/DbConnection/)"
// ❌ Bad: Hardcoded credentials
"Server=myserver;Database=mydb;User Id=admin;Password=P@ssw0rd123;"
API Keys and External Service Credentials
// ✅ Good: Options pattern with secrets
public class ExternalApiOptions
{
public string BaseUrl { get; set; } = string.Empty;
public string ApiKey { get; set; } = string.Empty;
}
services.Configure<ExternalApiOptions>(
configuration.GetSection("ExternalApi"));
// Usage
public class ApiClient
{
private readonly ExternalApiOptions _options;
public ApiClient(IOptions<ExternalApiOptions> options)
{
_options = options.Value;
}
}
Input Validation and Sanitization
Request Validation with FluentValidation
public class CreateUserRequestValidator : AbstractValidator<CreateUserRequest>
{
public CreateUserRequestValidator()
{
RuleFor(x => x.Email)
.NotEmpty()
.EmailAddress()
.MaximumLength(256);
RuleFor(x => x.Username)
.NotEmpty()
.MinimumLength(3)
.MaximumLength(50)
.Matches(@"^[a-zA-Z0-9_]+$")
.WithMessage("Username can only contain letters, numbers, and underscores");
RuleFor(x => x.Password)
.NotEmpty()
.MinimumLength(12)
.Matches(@"[A-Z]").WithMessage("Password must contain uppercase letter")
.Matches(@"[a-z]").WithMessage("Password must contain lowercase letter")
.Matches(@"[0-9]").WithMessage("Password must contain digit")
.Matches(@"[^a-zA-Z0-9]").WithMessage("Password must contain special character");
}
}
Output Encoding
// ✅ Good: HTML encoding (automatic in Razor)
<p>@Model.UserInput</p> // Automatically encoded
// ✅ Good: Manual encoding when needed
@Html.Raw(HtmlEncoder.Default.Encode(Model.UserInput))
// ✅ Good: JavaScript encoding
var data = @Json.Serialize(Model.Data);
// ❌ Bad: Unencoded output
@Html.Raw(Model.UserInput) // XSS vulnerability!
Authentication and Authorization Best Practices
JWT Token Security
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = configuration["Jwt:Issuer"],
ValidAudience = configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(configuration["Jwt:Key"]!)),
ClockSkew = TimeSpan.Zero // Reduce clock skew for tighter expiry
};
});
Policy-Based Authorization
services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin"));
options.AddPolicy("CanEditDocument", policy =>
policy.Requirements.Add(new DocumentOwnerRequirement()));
options.AddPolicy("PremiumFeature", policy =>
policy.RequireClaim("subscription", "premium", "enterprise"));
});
Security Checklist
- [ ] All inputs validated and sanitized
- [ ] Parameterized queries used (no string concatenation)
- [ ] Authentication and authorization on all endpoints
- [ ] HTTPS enforced with HSTS
- [ ] Security headers configured
- [ ] Secrets stored in secure vault (not in code/config)
- [ ] Dependencies regularly updated and scanned
- [ ] Security events logged and monitored
- [ ] Error messages don't leak sensitive information
- [ ] CORS properly configured
- [ ] Rate limiting implemented on sensitive endpoints
- [ ] Session management secure (HttpOnly, Secure, SameSite cookies)