Asp.Net Core Security OWASP Created: 25 Jan 2026 Updated: 25 Jan 2026

Comprehensive Guide to OWASP Top 10 2021 Security Implementation in ASP.NET Core

The OWASP Top 10 represents the most critical security risks facing web applications today. This comprehensive guide provides practical, production-ready implementations for all ten OWASP Top 10 2021 categories using ASP.NET Core and .NET 10. Through detailed code examples, architectural patterns, and security best practices, developers will learn how to build secure APIs that protect against common vulnerabilities including broken access control, injection attacks, cryptographic failures, and more. Each category is addressed with real-world scenarios, complete implementations, and testing strategies.

1. Introduction

The Open Web Application Security Project (OWASP) Top 10 is the industry-standard document that provides awareness of the most critical web application security risks. First published in 2003 and updated regularly, the OWASP Top 10 serves as a guide for developers, security professionals, and organizations to understand and mitigate the most prevalent security threats.

Why OWASP Top 10 Matters

  1. Industry Standard: Recognized globally as the baseline for web application security
  2. Compliance Requirements: Many security standards reference OWASP Top 10
  3. Risk-Based Approach: Focuses on the most impactful vulnerabilities
  4. Practical Guidance: Provides actionable recommendations for developers

This Guide Covers All OWASP Top 10 2021 Categories

This article presents comprehensive security implementations for all ten OWASP Top 10 2021 categories with practical ASP.NET Core examples:

  1. A01:2021 – Broken Access Control
  2. A02:2021 – Cryptographic Failures
  3. A03:2021 – Injection
  4. A04:2021 – Insecure Design
  5. A05:2021 – Security Misconfiguration
  6. A06:2021 – Vulnerable and Outdated Components
  7. A07:2021 – Identification and Authentication Failures
  8. A08:2021 – Software and Data Integrity Failures
  9. A09:2021 – Security Logging and Monitoring Failures
  10. A10:2021 – Server-Side Request Forgery (SSRF)

2. OWASP Top 10 2021: Complete Overview

Understanding the OWASP Top 10 2021 Changes

The 2021 edition introduced significant updates:

  1. Three new categories were added
  2. Four categories were renamed
  3. Some categories were consolidated based on real-world data

The Complete List with Practical Focus

  1. A01:2021 – Broken Access Control ⬆️ (moved up from #5)
  2. Impact: Unauthorized access to resources and data
  3. Examples: URL manipulation, privilege escalation, CORS misconfiguration
  4. Focus: Authorization patterns, role-based access, resource-level permissions
  5. A02:2021 – Cryptographic Failures 🆕 (renamed from Sensitive Data Exposure)
  6. Impact: Exposure of sensitive data, password theft
  7. Examples: Weak encryption, plain text storage, insecure protocols
  8. Focus: Hashing algorithms, encryption at rest and in transit, key management
  9. A03:2021 – Injection ⬇️ (dropped from #1)
  10. Impact: Data theft, data loss, denial of service
  11. Examples: SQL injection, NoSQL injection, command injection
  12. Focus: Input validation, parameterized queries, ORM usage
  13. A04:2021 – Insecure Design 🆕 (new category)
  14. Impact: Wide variety of vulnerabilities due to design flaws
  15. Examples: Missing threat modeling, lack of security controls
  16. Focus: Secure design patterns, threat modeling, defense in depth
  17. A05:2021 – Security Misconfiguration ⬇️ (dropped from #6)
  18. Impact: System compromise, data breach
  19. Examples: Default credentials, unnecessary features, missing security headers
  20. Focus: Configuration management, secure defaults, hardening
  21. A06:2021 – Vulnerable and Outdated Components ⬇️
  22. Impact: System takeover, data breach
  23. Examples: Unpatched libraries, EOL software, unknown dependencies
  24. Focus: Dependency management, regular updates, vulnerability scanning
  25. A07:2021 – Identification and Authentication Failures ⬇️ (renamed)
  26. Impact: Account takeover, identity theft
  27. Examples: Weak passwords, session fixation, missing MFA
  28. Focus: Strong authentication, session management, credential protection
  29. A08:2021 – Software and Data Integrity Failures 🆕 (new category)
  30. Impact: Unauthorized code execution, data corruption
  31. Examples: Insecure CI/CD, unsigned updates, serialization vulnerabilities
  32. Focus: Code signing, integrity verification, secure pipelines
  33. A09:2021 – Security Logging and Monitoring Failures ⬇️
  34. Impact: Delayed breach detection, forensic challenges
  35. Examples: Missing logs, insufficient monitoring, no alerting
  36. Focus: Comprehensive logging, monitoring, incident response
  37. A10:2021 – Server-Side Request Forgery (SSRF) 🆕 (new category)
  38. Impact: Internal system access, data theft
  39. Examples: Unvalidated URLs, metadata service abuse
  40. Focus: URL validation, network segmentation, whitelisting

Architecture Overview for This Guide

We'll build a complete ASP.NET Core API implementing security controls for all ten categories:

┌─────────────────────────────────────────────────────────────┐
│ ASP.NET Core API │
│ (.NET 10) │
└───────────────────────────┬─────────────────────────────────┘
┌───────────────────────────▼─────────────────────────────────┐
│ Security Middleware Pipeline │
├─────────────────────────────────────────────────────────────┤
│ 1. Security Headers Middleware (A05) │
│ 2. Injection Protection Middleware (A03) │
│ 3. Rate Limiting (A05) │
│ 4. CORS Configuration (A01, A05) │
│ 5. JWT Validation Logging (A09) │
│ 6. Authentication (A07) │
│ 7. Authorization (A01) │
│ 8. Custom Validation Middleware (A01, A07) │
└───────────────────────────┬─────────────────────────────────┘
┌───────────────────────────▼─────────────────────────────────┐
│ Application Layer │
├─────────────────────────────────────────────────────────────┤
│ • Secure Design Patterns (A04) │
│ • Repository Pattern with Access Control (A01, A04) │
│ • Input Validation & Sanitization (A03) │
│ • Audit Trail & Data Integrity (A08, A09) │
│ • Secure External Communication (A10) │
└───────────────────────────┬─────────────────────────────────┘
┌───────────────────────────▼─────────────────────────────────┐
│ Data Layer │
├─────────────────────────────────────────────────────────────┤
│ • Entity Framework Core (A03) │
│ • Encrypted Fields (A02) │
│ • Audit Interceptor (A08, A09) │
│ • Change Tracking (A08) │
└─────────────────────────────────────────────────────────────┘

3. A01:2021 – Broken Access Control

3.1 Understanding the Threat

Broken Access Control jumped from #5 to #1 in 2021, representing 94% of applications tested having some form of broken access control. This category includes:

  1. Violation of least privilege: Users have more permissions than needed
  2. Bypassing access control checks: URL manipulation, force browsing
  3. Elevation of privilege: Acting as admin without being logged in
  4. Metadata manipulation: Tampering JWT tokens, cookies
  5. CORS misconfiguration: Allowing unauthorized domains

3.2 Real-World Attack Scenarios

Scenario 1: Insecure Direct Object Reference (IDOR)

GET /api/users/123/profile
→ Attacker changes to /api/users/456/profile
→ Accesses another user's data

Scenario 2: Function Level Access Control Missing

POST /api/admin/delete-user
→ Regular user can access admin endpoints
→ Deletes other users

3.3 ASP.NET Core Implementation

Role-Based Authorization

// Program.cs - Configure authorization policies
builder.Services.AddAuthorization(options =>
{
// Simple role-based policy
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin"));
// Multiple roles
options.AddPolicy("ModeratorOrAdmin", policy =>
policy.RequireRole("Admin", "Moderator"));
// Claims-based policy
options.AddPolicy("CanManageUsers", policy =>
policy.RequireClaim("Permission", "users.manage"));
// Custom requirement policy
options.AddPolicy("OwnerOrAdmin", policy =>
policy.Requirements.Add(new ResourceOwnerRequirement()));
});

// Custom authorization requirement
public class ResourceOwnerRequirement : IAuthorizationRequirement { }

public class ResourceOwnerHandler : AuthorizationHandler<ResourceOwnerRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;

public ResourceOwnerHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}

protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
ResourceOwnerRequirement requirement)
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null)
{
context.Fail();
return Task.CompletedTask;
}

var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var resourceOwnerId = httpContext.GetRouteValue("userId")?.ToString();

// Check if user is admin OR owns the resource
if (context.User.IsInRole("Admin") || userId == resourceOwnerId)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}

return Task.CompletedTask;
}
}

// Register handler
builder.Services.AddSingleton<IAuthorizationHandler, ResourceOwnerHandler>();
builder.Services.AddHttpContextAccessor();

Resource-Level Authorization in Endpoints

namespace SecurityApp.API.Endpoints;

public static class UserEndpoints
{
public static IEndpointRouteBuilder MapUserEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api/users").WithTags("Users");

// ❌ INSECURE - No authorization check
group.MapGet("/{userId}/profile", async (string userId, ApplicationDbContext db) =>
{
var user = await db.Users.FindAsync(userId);
return Results.Ok(user);
});

// ✅ SECURE - Policy-based authorization
group.MapGet("/{userId}/profile-secure", async (
string userId,
HttpContext httpContext,
ApplicationDbContext db) =>
{
var currentUserId = httpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var isAdmin = httpContext.User.IsInRole("Admin");

// Check authorization
if (currentUserId != userId && !isAdmin)
{
return Results.Forbid();
}

var user = await db.Users.FindAsync(userId);
return user != null ? Results.Ok(user) : Results.NotFound();
})
.RequireAuthorization(); // Requires authentication

// ✅ SECURE - Using custom policy
group.MapPut("/{userId}/update", async (
string userId,
[FromBody] UpdateUserRequest request,
ApplicationDbContext db) =>
{
var user = await db.Users.FindAsync(userId);
if (user == null) return Results.NotFound();

user.FirstName = request.FirstName;
user.LastName = request.LastName;
await db.SaveChangesAsync();

return Results.Ok(user);
})
.RequireAuthorization("OwnerOrAdmin");

// ✅ SECURE - Admin only
group.MapDelete("/{userId}", async (
string userId,
ApplicationDbContext db) =>
{
var user = await db.Users.FindAsync(userId);
if (user == null) return Results.NotFound();

db.Users.Remove(user);
await db.SaveChangesAsync();

return Results.NoContent();
})
.RequireAuthorization("AdminOnly");

return app;
}
}

public record UpdateUserRequest(string FirstName, string LastName);

Repository Pattern with Built-in Authorization

namespace SecurityApp.API.Repositories;

public interface ISecureRepository<T> where T : class
{
Task<T?> GetByIdAsync(string id, string requestingUserId, string[] roles);
Task<IEnumerable<T>> GetAllAsync(string requestingUserId, string[] roles);
Task<bool> UpdateAsync(T entity, string requestingUserId, string[] roles);
Task<bool> DeleteAsync(string id, string requestingUserId, string[] roles);
}

public class UserSecureRepository : ISecureRepository<ApplicationUser>
{
private readonly ApplicationDbContext _context;
private readonly ILogger<UserSecureRepository> _logger;

public UserSecureRepository(
ApplicationDbContext context,
ILogger<UserSecureRepository> logger)
{
_context = context;
_logger = logger;
}

public async Task<ApplicationUser?> GetByIdAsync(
string id,
string requestingUserId,
string[] roles)
{
// Admins can view any user
if (roles.Contains("Admin"))
{
return await _context.Users.FindAsync(id);
}

// Users can only view themselves
if (id != requestingUserId)
{
_logger.LogWarning(
"Access denied: User {RequestingUserId} attempted to view user {TargetUserId}",
requestingUserId, id);
return null;
}

return await _context.Users.FindAsync(id);
}

public async Task<IEnumerable<ApplicationUser>> GetAllAsync(
string requestingUserId,
string[] roles)
{
// Only admins can list all users
if (!roles.Contains("Admin"))
{
_logger.LogWarning(
"Access denied: Non-admin user {UserId} attempted to list all users",
requestingUserId);
return Enumerable.Empty<ApplicationUser>();
}

return await _context.Users.ToListAsync();
}

public async Task<bool> UpdateAsync(
ApplicationUser entity,
string requestingUserId,
string[] roles)
{
// Users can only update themselves, admins can update anyone
if (entity.Id != requestingUserId && !roles.Contains("Admin"))
{
_logger.LogWarning(
"Access denied: User {RequestingUserId} attempted to update user {TargetUserId}",
requestingUserId, entity.Id);
return false;
}

_context.Users.Update(entity);
await _context.SaveChangesAsync();
return true;
}

public async Task<bool> DeleteAsync(
string id,
string requestingUserId,
string[] roles)
{
// Only admins can delete users
if (!roles.Contains("Admin"))
{
_logger.LogWarning(
"Access denied: Non-admin user {UserId} attempted to delete user {TargetUserId}",
requestingUserId, id);
return false;
}

var user = await _context.Users.FindAsync(id);
if (user == null) return false;

_context.Users.Remove(user);
await _context.SaveChangesAsync();
return true;
}
}

CORS Configuration (Preventing Unauthorized Cross-Origin Access)

// Program.cs
builder.Services.AddCors(options =>
{
// ❌ INSECURE - Allows any origin
options.AddPolicy("InsecurePolicy", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});

// ✅ SECURE - Specific origins only
options.AddPolicy("SecurePolicy", policy =>
{
policy.WithOrigins(
"https://yourdomain.com",
"https://www.yourdomain.com",
"https://app.yourdomain.com")
.AllowedMethods("GET", "POST", "PUT", "DELETE")
.AllowedHeaders("Authorization", "Content-Type", "Accept")
.AllowCredentials()
.SetPreflightMaxAge(TimeSpan.FromHours(1));
});

// ✅ SECURE - Configuration-based origins
options.AddPolicy("ProductionPolicy", policy =>
{
var allowedOrigins = builder.Configuration
.GetSection("Cors:AllowedOrigins")
.Get<string[]>() ?? Array.Empty<string>();

policy.WithOrigins(allowedOrigins)
.AllowedMethods("GET", "POST", "PUT", "DELETE")
.AllowedHeaders("Authorization", "Content-Type")
.AllowCredentials();
});
});

// In Middleware pipeline
app.UseCors("SecurePolicy");

3.4 Testing Access Control

[Fact]
public async Task GetUserProfile_AsOwner_ShouldSucceed()
{
// Arrange
var userId = "user-123";
var token = GenerateTokenForUser(userId, new[] { "User" });

// Act
var response = await _client.GetAsync($"/api/users/{userId}/profile-secure",
request => request.Headers.Authorization = new("Bearer", token));

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

[Fact]
public async Task GetUserProfile_AsOtherUser_ShouldForbid()
{
// Arrange
var userId = "user-123";
var otherUserId = "user-456";
var token = GenerateTokenForUser(otherUserId, new[] { "User" });

// Act
var response = await _client.GetAsync($"/api/users/{userId}/profile-secure",
request => request.Headers.Authorization = new("Bearer", token));

// Assert
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
}

[Fact]
public async Task GetUserProfile_AsAdmin_ShouldSucceed()
{
// Arrange
var userId = "user-123";
var token = GenerateTokenForUser("admin-789", new[] { "Admin" });

// Act
var response = await _client.GetAsync($"/api/users/{userId}/profile-secure",
request => request.Headers.Authorization = new("Bearer", token));

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

[Fact]
public async Task DeleteUser_AsRegularUser_ShouldForbid()
{
// Arrange
var userId = "user-123";
var token = GenerateTokenForUser("user-456", new[] { "User" });

// Act
var response = await _client.DeleteAsync($"/api/users/{userId}",
request => request.Headers.Authorization = new("Bearer", token));

// Assert
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
}

Key Takeaways for A01:

  1. ✅ Implement authorization at multiple layers (middleware, endpoint, repository)
  2. ✅ Use policy-based authorization for complex scenarios
  3. ✅ Always validate resource ownership
  4. ✅ Configure CORS with specific origins
  5. ✅ Log authorization failures for security monitoring
  6. ✅ Default to deny access, explicitly grant permissions
  7. ✅ Test all access control scenarios

4. A02:2021 – Cryptographic Failures

4.1 Understanding the Threat

Previously known as "Sensitive Data Exposure," this category focuses on failures related to cryptography (or lack thereof). Common issues include:

  1. Transmitting data in clear text: HTTP instead of HTTPS
  2. Using weak cryptographic algorithms: MD5, SHA1 for passwords
  3. Improper key management: Hardcoded keys, weak keys
  4. Not encrypting sensitive data: Passwords, PII, credit cards
  5. Inadequate random number generation: Predictable tokens

4.2 ASP.NET Core Implementation

Password Hashing with Identity

// Program.cs - Configure password hashing
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
// Strong password requirements
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequiredLength = 12; // Minimum 12 characters
options.Password.RequiredUniqueChars = 4;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
// Use PBKDF2 with 100,000 iterations (ASP.NET Core Identity default)
.AddPasswordHasher<ApplicationUser>();

// Custom password hasher if needed
public class CustomPasswordHasher : IPasswordHasher<ApplicationUser>
{
public string HashPassword(ApplicationUser user, string password)
{
// Use Argon2id (winner of Password Hashing Competition)
return Argon2.Hash(password);
}

public PasswordVerificationResult VerifyHashedPassword(
ApplicationUser user, string hashedPassword, string providedPassword)
{
if (Argon2.Verify(hashedPassword, providedPassword))
{
return PasswordVerificationResult.Success;
}

return PasswordVerificationResult.Failed;
}
}

Data Encryption at Rest

using System.Security.Cryptography;
using System.Text;

namespace SecurityApp.API.Services;

public interface IEncryptionService
{
string Encrypt(string plainText);
string Decrypt(string cipherText);
}

public class AesEncryptionService : IEncryptionService
{
private readonly byte[] _key;
private readonly byte[] _iv;

public AesEncryptionService(IConfiguration configuration)
{
// Load from configuration or key vault
var keyString = configuration["Encryption:Key"]
?? throw new InvalidOperationException("Encryption key not configured");
var ivString = configuration["Encryption:IV"]
?? throw new InvalidOperationException("Encryption IV not configured");

_key = Convert.FromBase64String(keyString);
_iv = Convert.FromBase64String(ivString);

// Validate key sizes
if (_key.Length != 32) // 256-bit key
throw new ArgumentException("Key must be 256 bits");
if (_iv.Length != 16) // 128-bit IV
throw new ArgumentException("IV must be 128 bits");
}

public string Encrypt(string plainText)
{
if (string.IsNullOrEmpty(plainText))
throw new ArgumentNullException(nameof(plainText));

using var aes = Aes.Create();
aes.Key = _key;
aes.IV = _iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;

var encryptor = aes.CreateEncryptor();

using var ms = new MemoryStream();
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
using (var sw = new StreamWriter(cs))
{
sw.Write(plainText);
}

return Convert.ToBase64String(ms.ToArray());
}

public string Decrypt(string cipherText)
{
if (string.IsNullOrEmpty(cipherText))
throw new ArgumentNullException(nameof(cipherText));

var buffer = Convert.FromBase64String(cipherText);

using var aes = Aes.Create();
aes.Key = _key;
aes.IV = _iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;

var decryptor = aes.CreateDecryptor();

using var ms = new MemoryStream(buffer);
using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
using var sr = new StreamReader(cs);

return sr.ReadToEnd();
}
}

// Entity with encrypted field
public class SensitiveData
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string UserId { get; set; } = string.Empty;
// Store encrypted
public string EncryptedSocialSecurity { get; set; } = string.Empty;
public string EncryptedCreditCard { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

// Service layer encryption
public class SensitiveDataService
{
private readonly ApplicationDbContext _context;
private readonly IEncryptionService _encryption;

public SensitiveDataService(
ApplicationDbContext context,
IEncryptionService encryption)
{
_context = context;
_encryption = encryption;
}

public async Task SaveSensitiveDataAsync(
string userId, string ssn, string creditCard)
{
var data = new SensitiveData
{
UserId = userId,
EncryptedSocialSecurity = _encryption.Encrypt(ssn),
EncryptedCreditCard = _encryption.Encrypt(creditCard)
};

_context.SensitiveData.Add(data);
await _context.SaveChangesAsync();
}

public async Task<(string ssn, string creditCard)?> GetSensitiveDataAsync(
string userId)
{
var data = await _context.SensitiveData
.FirstOrDefaultAsync(d => d.UserId == userId);

if (data == null) return null;

return (
_encryption.Decrypt(data.EncryptedSocialSecurity),
_encryption.Decrypt(data.EncryptedCreditCard)
);
}
}

JWT Token Security

// Program.cs
var jwtOptions = builder.Configuration.GetSection("JwtOptions").Get<JwtOptions>()!;

// Validate JWT configuration
if (jwtOptions.SecretKey.Length < 32)
throw new InvalidOperationException("JWT secret key must be at least 256 bits (32 characters)");

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtOptions.Issuer,
ValidAudience = jwtOptions.Audience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(jwtOptions.SecretKey)),
ClockSkew = TimeSpan.Zero, // No tolerance for expiry
RequireExpirationTime = true,
RequireSignedTokens = true
};

// Enforce HTTPS
options.RequireHttpsMetadata = !builder.Environment.IsDevelopment();

// Token validation events
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// Only accept tokens from Authorization header
var token = context.Request.Headers.Authorization
.ToString().Replace("Bearer ", "");
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
}

return Task.CompletedTask;
}
};
});

// JWT Generation with strong algorithms
public class SecureJwtTokenService : IJwtTokenService
{
private readonly JwtOptions _options;

public string GenerateAccessToken(ApplicationUser user, IList<string> roles)
{
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, user.Id),
new(ClaimTypes.Email, user.Email!),
new(JwtRegisteredClaimNames.Sub, user.Id),
new(JwtRegisteredClaimNames.Email, user.Email!),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new(JwtRegisteredClaimNames.Iat,
DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())
};

foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}

// Use HMAC-SHA256 (HS256) - Symmetric algorithm
var securityKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_options.SecretKey));
var credentials = new SigningCredentials(
securityKey,
SecurityAlgorithms.HmacSha256); // Strong algorithm

var token = new JwtSecurityToken(
issuer: _options.Issuer,
audience: _options.Audience,
claims: claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddMinutes(_options.ExpirationMinutes),
signingCredentials: credentials
);

return new JwtSecurityTokenHandler().WriteToken(token);
}

public string GenerateRefreshToken()
{
// Use cryptographically secure random number generator
var randomBytes = new byte[64];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomBytes);
return Convert.ToBase64String(randomBytes);
}
}

HTTPS Enforcement

// Program.cs
builder.Services.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status308PermanentRedirect;
options.HttpsPort = 443;
});

builder.Services.AddHsts(options =>
{
options.Preload = true;
options.IncludeSubDomains = true;
options.MaxAge = TimeSpan.FromDays(365);
});

var app = builder.Build();

// Force HTTPS in production
if (!app.Environment.IsDevelopment())
{
app.UseHsts();
}

app.UseHttpsRedirection();

Key Takeaways for A02:

  1. ✅ Use strong hashing algorithms (Argon2, PBKDF2) for passwords
  2. ✅ Encrypt sensitive data at rest using AES-256
  3. ✅ Use HTTPS everywhere (enforce with HSTS)
  4. ✅ Generate JWT tokens with strong algorithms (HS256, RS256)
  5. ✅ Use cryptographically secure random generators
  6. ✅ Store encryption keys securely (Azure Key Vault, AWS KMS)
  7. ✅ Never hardcode secrets in source code
  8. ✅ Implement proper key rotation strategies

4.1 Problem Statement

Authentication failures occur when:

  1. Session management is improper
  2. Credentials are not properly protected
  3. Session IDs are exposed in URLs
  4. Tokens don't expire or have long expiration times
  5. Passwords, session IDs, and other credentials are sent over unencrypted connections

4.2 Solution: JWT Token Fingerprinting

Token fingerprinting binds the JWT token to the specific client environment, making stolen tokens useless if used from a different context.

Implementation: HTTP Context Extensions

using System.Security.Cryptography;
using System.Text;

namespace SecurityApp.API.Extensions;

public static class HttpContextExtensions
{
public static string GetClientIpAddress(this HttpContext context)
{
var forwardedFor = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (!string.IsNullOrEmpty(forwardedFor))
{
return forwardedFor.Split(',')[0].Trim();
}

var realIp = context.Request.Headers["X-Real-IP"].FirstOrDefault();
if (!string.IsNullOrEmpty(realIp))
{
return realIp;
}

return context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
}

public static string GetUserAgent(this HttpContext context)
{
return context.Request.Headers.UserAgent.FirstOrDefault() ?? "unknown";
}

public static string GetClientIpAddressHash(this HttpContext context)
{
var ip = GetClientIpAddress(context);
return ComputeSha256Hash(ip);
}

public static string GetUserAgentHash(this HttpContext context)
{
var userAgent = GetUserAgent(context);
return ComputeSha256Hash(userAgent);
}

private static string ComputeSha256Hash(string input)
{
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(input));
return Convert.ToHexString(bytes).ToLowerInvariant();
}
}

Key Security Benefits:

  1. ✅ Privacy protection: Hashed values prevent exposure of actual IP/User-Agent
  2. ✅ Deterministic: Same input always produces the same hash for validation
  3. ✅ Irreversible: Cannot reverse-engineer original values from hash
  4. ✅ Fixed size: SHA256 produces consistent 64-character hex strings

Enhanced JWT Token Service

using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using SecurityApp.API.Models;
using SecurityApp.API.Options;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;

namespace SecurityApp.API.Services;

public interface IJwtTokenService
{
string GenerateAccessToken(ApplicationUser user, IList<string> roles,
string clientIpHash, string userAgentHash);
string GenerateRefreshToken();
ClaimsPrincipal? GetPrincipalFromExpiredToken(string token);
}

public class JwtTokenService : IJwtTokenService
{
private readonly JwtOptions _jwtOptions;

public JwtTokenService(IOptions<JwtOptions> jwtOptions)
{
_jwtOptions = jwtOptions.Value;
}

public string GenerateAccessToken(ApplicationUser user, IList<string> roles,
string clientIpHash, string userAgentHash)
{
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, user.Id),
new(ClaimTypes.Name, user.UserName!),
new(ClaimTypes.Email, user.Email!),
new(JwtRegisteredClaimNames.Sub, user.Id),
new(JwtRegisteredClaimNames.Email, user.Email!),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new("client_ip_hash", clientIpHash),
new("user_agent_hash", userAgentHash)
};

if (!string.IsNullOrWhiteSpace(user.FirstName))
{
claims.Add(new Claim(ClaimTypes.GivenName, user.FirstName));
}

if (!string.IsNullOrWhiteSpace(user.LastName))
{
claims.Add(new Claim(ClaimTypes.Surname, user.LastName));
}

foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}

var securityKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_jwtOptions.SecretKey));
var credentials = new SigningCredentials(
securityKey, SecurityAlgorithms.HmacSha256);

var token = new JwtSecurityToken(
issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(_jwtOptions.ExpirationMinutes),
signingCredentials: credentials
);

return new JwtSecurityTokenHandler().WriteToken(token);
}

public string GenerateRefreshToken()
{
var randomBytes = new byte[64];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomBytes);
return Convert.ToBase64String(randomBytes);
}

public ClaimsPrincipal? GetPrincipalFromExpiredToken(string token)
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_jwtOptions.SecretKey)),
ValidateLifetime = false
};

var tokenHandler = new JwtSecurityTokenHandler();
var principal = tokenHandler.ValidateToken(token,
tokenValidationParameters, out var securityToken);
if (securityToken is not JwtSecurityToken jwtSecurityToken ||
!jwtSecurityToken.Header.Alg.Equals(
SecurityAlgorithms.HmacSha256,
StringComparison.InvariantCultureIgnoreCase))
{
throw new SecurityTokenException("Invalid token");
}

return principal;
}
}

4.3 Strong Password Policies

// In Program.cs
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequiredLength = 8;

// Lockout settings - Protection against brute force
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;

// User settings
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

Security Features:

  1. ✅ Enforces complex password requirements
  2. ✅ Implements account lockout after failed attempts
  3. ✅ Prevents brute force attacks
  4. ✅ Ensures email uniqueness

5. Addressing OWASP A01:2021 – Broken Access Control

5.1 Problem Statement

Broken access control allows unauthorized users to:

  1. Access functionality or data they shouldn't
  2. Modify or delete data
  3. Perform actions outside their permissions
  4. Bypass access control checks

5.2 Solution: IP and User-Agent Validation Middleware

IP Validation Middleware

using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;

namespace SecurityApp.API.Middleware;

public class IpValidationMiddleware
{
private readonly RequestDelegate _next;

public IpValidationMiddleware(RequestDelegate next)
{
_next = next;
}

public async Task InvokeAsync(HttpContext context)
{
if (context.User.Identity?.IsAuthenticated == true)
{
var tokenIpHashClaim = context.User.FindFirst("client_ip_hash")?.Value;

if (!string.IsNullOrEmpty(tokenIpHashClaim))
{
var currentIp = GetClientIpAddress(context);
var currentIpHash = ComputeSha256Hash(currentIp);

if (tokenIpHashClaim != currentIpHash)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
await context.Response.WriteAsJsonAsync(new
{
Message = "IP address mismatch. Token was issued for a different IP address."
});
return;
}
}
}

await _next(context);
}

private static string GetClientIpAddress(HttpContext context)
{
var forwardedFor = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (!string.IsNullOrEmpty(forwardedFor))
{
return forwardedFor.Split(',')[0].Trim();
}

var realIp = context.Request.Headers["X-Real-IP"].FirstOrDefault();
if (!string.IsNullOrEmpty(realIp))
{
return realIp;
}

return context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
}

private static string ComputeSha256Hash(string input)
{
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(input));
return Convert.ToHexString(bytes).ToLowerInvariant();
}
}

User-Agent Validation Middleware

using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;

namespace SecurityApp.API.Middleware;

public class UserAgentValidationMiddleware
{
private readonly RequestDelegate _next;

public UserAgentValidationMiddleware(RequestDelegate next)
{
_next = next;
}

public async Task InvokeAsync(HttpContext context)
{
if (context.User.Identity?.IsAuthenticated == true)
{
var tokenUserAgentHashClaim = context.User.FindFirst("user_agent_hash")?.Value;

if (!string.IsNullOrEmpty(tokenUserAgentHashClaim))
{
var currentUserAgent = GetUserAgent(context);
var currentUserAgentHash = ComputeSha256Hash(currentUserAgent);

if (tokenUserAgentHashClaim != currentUserAgentHash)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
await context.Response.WriteAsJsonAsync(new
{
Message = "User-Agent mismatch. Token was issued for a different browser or device."
});
return;
}
}
}

await _next(context);
}

private static string GetUserAgent(HttpContext context)
{
return context.Request.Headers.UserAgent.FirstOrDefault() ?? "unknown";
}

private static string ComputeSha256Hash(string input)
{
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(input));
return Convert.ToHexString(bytes).ToLowerInvariant();
}
}

How It Prevents Broken Access Control:

  1. ✅ Binds tokens to specific client environments
  2. ✅ Prevents token theft and replay attacks
  3. ✅ Validates every authenticated request
  4. ✅ Immediate rejection of mismatched requests

5.3 Attack Scenario Prevention

Scenario: Stolen Token Attack

  1. Without Fingerprinting:
  2. Attacker steals JWT token (XSS, MITM, etc.)
  3. Attacker uses token from their device
  4. ✗ Attacker gains full access
  5. With Fingerprinting:
  6. Attacker steals JWT token
  7. Attacker uses token from their device
  8. Middleware detects IP/User-Agent mismatch
  9. ✓ Request rejected with 401 Unauthorized

6. Addressing OWASP A02:2021 – Cryptographic Failures

6.1 Problem Statement

Cryptographic failures include:

  1. Not using encryption for sensitive data
  2. Using weak or outdated cryptographic algorithms
  3. Improper key management
  4. Not enforcing encryption in transit

6.2 Solution: SHA256 Hashing and Strong JWT Configuration

Why SHA256 for Fingerprinting?

private static string ComputeSha256Hash(string input)
{
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(input));
return Convert.ToHexString(bytes).ToLowerInvariant();
}

Benefits:

  1. One-way function: Cannot reverse to get original value
  2. Collision resistant: Extremely unlikely two inputs produce same hash
  3. Deterministic: Same input always produces same output
  4. Fast: Efficient computation
  5. Fixed size: Always 256 bits (64 hex characters)

JWT Security Configuration

// In Program.cs
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtOptions.Issuer,
ValidAudience = jwtOptions.Audience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(jwtOptions.SecretKey)),
ClockSkew = TimeSpan.Zero // No clock skew tolerance
};
// ... event handlers (shown in next section)
});

Security Features:

  1. ✅ Validates issuer and audience
  2. ✅ Enforces token expiration strictly (ClockSkew = 0)
  3. ✅ Validates signing key
  4. ✅ Uses HMAC-SHA256 for token signing

6.3 Secure Token Storage

Best Practices Implemented:

  1. Access tokens: Short-lived (60 minutes)
  2. Refresh tokens: Longer-lived (7 days), stored in database
  3. No sensitive data in JWT payload
  4. Hashed fingerprints instead of raw values

7. Addressing OWASP A09:2021 – Security Logging and Monitoring Failures

7.1 Problem Statement

Insufficient logging and monitoring leads to:

  1. Inability to detect breaches in time
  2. No audit trail for investigations
  3. Missing critical security events
  4. Lack of alerting mechanisms

7.2 Solution: Comprehensive JWT Validation Logging

JWT Validation Details Middleware

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Text;

namespace SecurityApp.API.Middleware;

public class JwtValidationDetailsMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<JwtValidationDetailsMiddleware> _logger;

public JwtValidationDetailsMiddleware(RequestDelegate next,
ILogger<JwtValidationDetailsMiddleware> logger)
{
_next = next;
_logger = logger;
}

public async Task InvokeAsync(HttpContext context)
{
var authHeader = context.Request.Headers.Authorization.FirstOrDefault();
if (!string.IsNullOrEmpty(authHeader) &&
authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
var token = authHeader["Bearer ".Length..].Trim();
try
{
var handler = new JwtSecurityTokenHandler();
var jwtToken = handler.ReadJwtToken(token);
var now = DateTime.UtcNow;
var validFrom = jwtToken.ValidFrom;
var validTo = jwtToken.ValidTo;
_logger.LogInformation(
"JWT Token Details - Issuer: {Issuer}, Audience: {Audience}, " +
"ValidFrom: {ValidFrom}, ValidTo: {ValidTo}, Now: {Now}, " +
"IsExpired: {IsExpired}",
jwtToken.Issuer,
string.Join(", ", jwtToken.Audiences),
validFrom,
validTo,
now,
now > validTo);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to read JWT token details");
}
}
await _next(context);
}
}

Detailed JWT Bearer Events

options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<Program>>();
if (context.Exception is SecurityTokenExpiredException expiredException)
{
logger.LogWarning("Token expired at {ExpiredTime}",
expiredException.Expires);
context.Response.Headers.Append("Token-Expired", "true");
}
else if (context.Exception is SecurityTokenInvalidIssuerException)
{
logger.LogWarning("Invalid Issuer: {Message}",
context.Exception.Message);
context.Response.Headers.Append("Token-Error", "Invalid Issuer");
}
else if (context.Exception is SecurityTokenInvalidAudienceException)
{
logger.LogWarning("Invalid Audience: {Message}",
context.Exception.Message);
context.Response.Headers.Append("Token-Error", "Invalid Audience");
}
else if (context.Exception is SecurityTokenInvalidSignatureException)
{
logger.LogWarning("Invalid Signature: {Message}",
context.Exception.Message);
context.Response.Headers.Append("Token-Error", "Invalid Signature");
}
else
{
logger.LogError(context.Exception,
"Authentication failed: {Message}",
context.Exception.Message);
context.Response.Headers.Append("Token-Error",
context.Exception.GetType().Name);
}

return Task.CompletedTask;
},
OnTokenValidated = context =>
{
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<Program>>();
var userEmail = context.Principal?.Identity?.Name;
logger.LogInformation(
"Token validated successfully for user: {UserEmail}", userEmail);
return Task.CompletedTask;
},
OnChallenge = context =>
{
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<Program>>();
logger.LogWarning(
"Authentication challenge. Error: {Error}, " +
"ErrorDescription: {ErrorDescription}",
context.Error, context.ErrorDescription);
return Task.CompletedTask;
}
};

7.3 Debug Endpoint for Token Validation

private static IResult ValidateToken(HttpContext httpContext)
{
var authHeader = httpContext.Request.Headers.Authorization.FirstOrDefault();
if (string.IsNullOrEmpty(authHeader) ||
!authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
return Results.BadRequest(new { Message = "No bearer token provided" });
}

var token = authHeader["Bearer ".Length..].Trim();
var handler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
try
{
var jwtToken = handler.ReadJwtToken(token);
var now = DateTime.UtcNow;
return Results.Ok(new
{
Issuer = jwtToken.Issuer,
Audiences = jwtToken.Audiences,
ValidFrom = jwtToken.ValidFrom,
ValidTo = jwtToken.ValidTo,
CurrentTime = now,
IsExpired = now > jwtToken.ValidTo,
TimeUntilExpiry = jwtToken.ValidTo - now,
Claims = jwtToken.Claims.Select(c => new { c.Type, c.Value })
});
}
catch (Exception ex)
{
return Results.BadRequest(new
{
Message = "Invalid token format",
Error = ex.Message
});
}
}

Logging Benefits:

  1. ✅ Real-time token validation monitoring
  2. ✅ Detailed failure reasons (expiry, issuer, audience, signature)
  3. ✅ Audit trail for security investigations
  4. ✅ Debug endpoint for troubleshooting
  5. ✅ Custom response headers for client-side handling

8. Addressing OWASP A03:2021 – Injection

8.1 Problem Statement

Injection flaws occur when untrusted data is sent to an interpreter as part of a command or query. Common types include:

  1. SQL Injection
  2. NoSQL Injection
  3. OS Command Injection
  4. LDAP Injection

8.2 Solution: Parameterized Queries and Input Validation

SQL Injection Prevention with Entity Framework

// ❌ VULNERABLE CODE - Never do this!
public async Task<ApplicationUser?> GetUserByEmailVulnerable(string email)
{
// Direct string concatenation - SQL Injection risk!
var query = $"SELECT * FROM Users WHERE Email = '{email}'";
return await _context.Users.FromSqlRaw(query).FirstOrDefaultAsync();
}

// ✅ SECURE CODE - Use parameterized queries
public async Task<ApplicationUser?> GetUserByEmailSecure(string email)
{
// Entity Framework automatically parameterizes
return await _context.Users
.FirstOrDefaultAsync(u => u.Email == email);
}

// ✅ SECURE CODE - Explicit parameterization if needed
public async Task<ApplicationUser?> GetUserByEmailWithParameter(string email)
{
return await _context.Users
.FromSqlRaw("SELECT * FROM Users WHERE Email = {0}", email)
.FirstOrDefaultAsync();
}

Input Validation Attribute

using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;

namespace SecurityApp.API.Validation;

public class SafeStringAttribute : ValidationAttribute
{
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
if (value is string input)
{
// Prevent common injection patterns
var dangerousPatterns = new[]
{
@"<script",
@"javascript:",
@"onerror=",
@"onload=",
@"eval\(",
@"exec\(",
@"';\s*DROP",
@"--",
@"/*",
@"xp_"
};

foreach (var pattern in dangerousPatterns)
{
if (Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase))
{
return new ValidationResult($"Input contains potentially dangerous content.");
}
}
}
return ValidationResult.Success;
}
}

// Usage in DTOs
public class RegisterRequest
{
[Required]
[EmailAddress]
[SafeString]
public string Email { get; set; } = string.Empty;

[Required]
[SafeString]
[StringLength(100, MinimumLength = 2)]
public string FirstName { get; set; } = string.Empty;

[Required]
[SafeString]
[StringLength(100, MinimumLength = 2)]
public string LastName { get; set; } = string.Empty;

[Required]
[StringLength(100, MinimumLength = 8)]
public string Password { get; set; } = string.Empty;
}

Request Validation Middleware

using System.Text.RegularExpressions;

namespace SecurityApp.API.Middleware;

public class InjectionProtectionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<InjectionProtectionMiddleware> _logger;

private static readonly Regex[] DangerousPatterns = new[]
{
new Regex(@"(\bOR\b|\bAND\b).*=.*", RegexOptions.IgnoreCase),
new Regex(@";\s*DROP\s+TABLE", RegexOptions.IgnoreCase),
new Regex(@"<script[^>]*>.*?</script>", RegexOptions.IgnoreCase | RegexOptions.Singleline),
new Regex(@"javascript\s*:", RegexOptions.IgnoreCase),
new Regex(@"\bexec\s*\(", RegexOptions.IgnoreCase),
new Regex(@"union\s+select", RegexOptions.IgnoreCase)
};

public InjectionProtectionMiddleware(RequestDelegate next,
ILogger<InjectionProtectionMiddleware> logger)
{
_next = next;
_logger = logger;
}

public async Task InvokeAsync(HttpContext context)
{
var queryString = context.Request.QueryString.Value;
if (!string.IsNullOrEmpty(queryString))
{
foreach (var pattern in DangerousPatterns)
{
if (pattern.IsMatch(queryString))
{
_logger.LogWarning(
"Potential injection attempt detected. IP: {IP}, Query: {Query}",
context.Connection.RemoteIpAddress,
queryString);
context.Response.StatusCode = StatusCodes.Status400BadRequest;
await context.Response.WriteAsJsonAsync(new
{
Message = "Invalid request detected."
});
return;
}
}
}

await _next(context);
}
}

Security Benefits:

  1. ✅ Parameterized queries prevent SQL injection
  2. ✅ Input validation catches malicious patterns
  3. ✅ Middleware provides defense in depth
  4. ✅ Logging helps identify attack attempts

9. Addressing OWASP A04:2021 – Insecure Design

9.1 Problem Statement

Insecure design represents missing or ineffective security controls in the design phase:

  1. Lack of security requirements
  2. No threat modeling
  3. Missing security controls
  4. Inadequate separation of concerns

9.2 Solution: Secure Design Patterns

Repository Pattern with Security Controls

namespace SecurityApp.API.Repositories;

public interface IUserRepository
{
Task<ApplicationUser?> GetByIdAsync(string userId, string requestingUserId);
Task<ApplicationUser?> GetByEmailAsync(string email);
Task<IEnumerable<ApplicationUser>> GetAllAsync(string requestingUserId, string[] roles);
Task<bool> UpdateAsync(ApplicationUser user, string requestingUserId);
}

public class UserRepository : IUserRepository
{
private readonly ApplicationDbContext _context;
private readonly ILogger<UserRepository> _logger;

public UserRepository(ApplicationDbContext context, ILogger<UserRepository> logger)
{
_context = context;
_logger = logger;
}

// Secure by design - always requires requesting user context
public async Task<ApplicationUser?> GetByIdAsync(string userId, string requestingUserId)
{
// Authorization check at data layer
if (userId != requestingUserId)
{
_logger.LogWarning(
"User {RequestingUser} attempted to access user {TargetUser}",
requestingUserId, userId);
return null;
}

return await _context.Users
.AsNoTracking()
.FirstOrDefaultAsync(u => u.Id == userId);
}

public async Task<ApplicationUser?> GetByEmailAsync(string email)
{
return await _context.Users
.AsNoTracking()
.FirstOrDefaultAsync(u => u.Email == email);
}

// Only admins can list all users
public async Task<IEnumerable<ApplicationUser>> GetAllAsync(
string requestingUserId, string[] roles)
{
if (!roles.Contains("Admin"))
{
_logger.LogWarning(
"Non-admin user {UserId} attempted to list all users",
requestingUserId);
return Enumerable.Empty<ApplicationUser>();
}

return await _context.Users
.AsNoTracking()
.ToListAsync();
}

// Users can only update their own data
public async Task<bool> UpdateAsync(ApplicationUser user, string requestingUserId)
{
if (user.Id != requestingUserId)
{
_logger.LogWarning(
"User {RequestingUser} attempted to update user {TargetUser}",
requestingUserId, user.Id);
return false;
}

_context.Users.Update(user);
await _context.SaveChangesAsync();
return true;
}
}

Domain-Driven Security Model

namespace SecurityApp.API.Models;

public class SecureDocument
{
public string Id { get; private set; } = Guid.NewGuid().ToString();
public string OwnerId { get; private set; } = string.Empty;
public string Title { get; private set; } = string.Empty;
public string Content { get; private set; } = string.Empty;
public DocumentAccessLevel AccessLevel { get; private set; }
public DateTime CreatedAt { get; private set; }
public DateTime? ModifiedAt { get; private set; }
public bool IsDeleted { get; private set; }

private SecureDocument() { } // EF Core

// Factory method with built-in security
public static SecureDocument Create(string ownerId, string title,
string content, DocumentAccessLevel accessLevel)
{
if (string.IsNullOrWhiteSpace(ownerId))
throw new ArgumentException("Owner ID is required");
if (string.IsNullOrWhiteSpace(title))
throw new ArgumentException("Title is required");

return new SecureDocument
{
OwnerId = ownerId,
Title = title,
Content = content,
AccessLevel = accessLevel,
CreatedAt = DateTime.UtcNow
};
}

// Secure update - checks ownership
public Result Update(string requestingUserId, string newTitle, string newContent)
{
if (requestingUserId != OwnerId)
return Result.Failure("Unauthorized: Only the owner can update this document");

if (IsDeleted)
return Result.Failure("Cannot update deleted document");

Title = newTitle;
Content = newContent;
ModifiedAt = DateTime.UtcNow;

return Result.Success();
}

// Secure delete - soft delete with ownership check
public Result Delete(string requestingUserId)
{
if (requestingUserId != OwnerId)
return Result.Failure("Unauthorized: Only the owner can delete this document");

IsDeleted = true;
ModifiedAt = DateTime.UtcNow;

return Result.Success();
}

// Access control check
public bool CanAccess(string userId, string[] userRoles)
{
if (IsDeleted)
return false;

return AccessLevel switch
{
DocumentAccessLevel.Private => userId == OwnerId,
DocumentAccessLevel.Internal => userRoles.Contains("Employee"),
DocumentAccessLevel.Public => true,
_ => false
};
}
}

public enum DocumentAccessLevel
{
Private = 0,
Internal = 1,
Public = 2
}

public class Result
{
public bool IsSuccess { get; private set; }
public string Error { get; private set; } = string.Empty;

public static Result Success() => new() { IsSuccess = true };
public static Result Failure(string error) => new() { IsSuccess = false, Error = error };
}

Secure Design Principles:

  1. ✅ Security built into domain models
  2. ✅ Authorization checks at multiple layers
  3. ✅ Immutable properties prevent tampering
  4. ✅ Factory methods ensure valid object creation
  5. ✅ Explicit access control methods

10. Addressing OWASP A05:2021 – Security Misconfiguration

10.1 Problem Statement

Security misconfiguration includes:

  1. Missing security headers
  2. Verbose error messages in production
  3. Default accounts enabled
  4. Unnecessary features enabled
  5. Outdated software

10.2 Solution: Security Configuration Best Practices

Security Headers Middleware

namespace SecurityApp.API.Middleware;

public class SecurityHeadersMiddleware
{
private readonly RequestDelegate _next;

public SecurityHeadersMiddleware(RequestDelegate next)
{
_next = next;
}

public async Task InvokeAsync(HttpContext context)
{
// Remove server information disclosure
context.Response.Headers.Remove("Server");
context.Response.Headers.Remove("X-Powered-By");

// Prevent clickjacking
context.Response.Headers["X-Frame-Options"] = "DENY";

// Enable XSS protection
context.Response.Headers["X-XSS-Protection"] = "1; mode=block";

// Prevent MIME type sniffing
context.Response.Headers["X-Content-Type-Options"] = "nosniff";

// Content Security Policy
context.Response.Headers["Content-Security-Policy"] =
"default-src 'self'; " +
"script-src 'self'; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https:; " +
"font-src 'self'; " +
"connect-src 'self'; " +
"frame-ancestors 'none';";

// Referrer Policy
context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin";

// Permissions Policy
context.Response.Headers["Permissions-Policy"] =
"geolocation=(), microphone=(), camera=()";

// HSTS - Only in production
if (!context.Request.IsHttps)
{
context.Response.Redirect($"https://{context.Request.Host}{context.Request.Path}");
return;
}
context.Response.Headers["Strict-Transport-Security"] =
"max-age=31536000; includeSubDomains; preload";

await _next(context);
}
}

Secure Configuration in Program.cs

var builder = WebApplication.CreateBuilder(args);

// Configure CORS securely
builder.Services.AddCors(options =>
{
options.AddPolicy("SecurePolicy", policy =>
{
policy.WithOrigins(
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
?? Array.Empty<string>())
.AllowedMethods("GET", "POST", "PUT", "DELETE")
.AllowedHeaders("Authorization", "Content-Type")
.AllowCredentials()
.SetIsOriginAllowedToAllowWildcardSubdomains();
});
});

// Disable detailed errors in production
if (!builder.Environment.IsDevelopment())
{
builder.Services.AddExceptionHandler(options =>
{
options.ExceptionHandler = async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
await context.Response.WriteAsJsonAsync(new
{
Message = "An error occurred. Please contact support."
// No stack trace or detailed error info!
});
};
});
}

// Configure HTTPS redirection
builder.Services.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status308PermanentRedirect;
options.HttpsPort = 443;
});

// Rate limiting
builder.Services.AddRateLimiter(options =>
{
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: context.Connection.RemoteIpAddress?.ToString() ?? "unknown",
factory: partition => new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1),
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 0
}));
});

var app = builder.Build();

// Security middleware order
app.UseSecurityHeaders(); // Custom middleware
app.UseRateLimiter();
app.UseHttpsRedirection();
app.UseCors("SecurePolicy");

// Only enable Swagger in Development
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference();
}
else
{
app.UseExceptionHandler();
}

app.Run();

appsettings.json Configuration

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedOrigins": [
"https://yourdomain.com",
"https://www.yourdomain.com"
],
"JwtOptions": {
"SecretKey": "YOUR-STRONG-SECRET-KEY-MIN-32-CHARACTERS",
"Issuer": "https://api.yourdomain.com",
"Audience": "https://yourdomain.com",
"ExpirationMinutes": 60
},
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=SecurityAppDb;Trusted_Connection=true;TrustServerCertificate=true"
}
}

Configuration Best Practices:

  1. ✅ Security headers prevent common attacks
  2. ✅ HTTPS enforcement with HSTS
  3. ✅ CORS configured with specific origins
  4. ✅ Rate limiting prevents abuse
  5. ✅ Detailed errors disabled in production
  6. ✅ Sensitive configuration externalized

11. Addressing OWASP A06:2021 – Vulnerable and Outdated Components

11.1 Problem Statement

Using components with known vulnerabilities:

  1. Outdated libraries and frameworks
  2. Unsupported or unpatched dependencies
  3. Not tracking dependency vulnerabilities

11.2 Solution: Dependency Management and Scanning

NuGet Package Audit Configuration

<!-- SecurityApp.API.csproj -->
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>14.0</LangVersion>
<!-- Enable NuGet package vulnerability auditing -->
<NuGetAudit>true</NuGetAudit>
<NuGetAuditMode>all</NuGetAuditMode>
<NuGetAuditLevel>low</NuGetAuditLevel>
</PropertyGroup>

<ItemGroup>
<!-- Keep packages up to date -->
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!-- Security analyzers -->
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>

</Project>

Dependency Health Check Endpoint

namespace SecurityApp.API.Endpoints;

public static class HealthEndpoints
{
public static IEndpointRouteBuilder MapHealthEndpoints(this IEndpointRouteBuilder app)
{
app.MapGet("/health", () => Results.Ok(new
{
Status = "Healthy",
Timestamp = DateTime.UtcNow,
Version = typeof(Program).Assembly.GetName().Version?.ToString()
}))
.WithName("HealthCheck")
.AllowAnonymous();

app.MapGet("/health/dependencies", (IConfiguration config) =>
{
var dependencies = new
{
AspNetCore = typeof(Microsoft.AspNetCore.Builder.WebApplication)
.Assembly.GetName().Version?.ToString(),
EntityFrameworkCore = typeof(Microsoft.EntityFrameworkCore.DbContext)
.Assembly.GetName().Version?.ToString(),
AspNetCoreIdentity = typeof(Microsoft.AspNetCore.Identity.IdentityUser)
.Assembly.GetName().Version?.ToString(),
TargetFramework = "net10.0",
LastChecked = DateTime.UtcNow
};

return Results.Ok(dependencies);
})
.WithName("DependencyCheck")
.RequireAuthorization(policy => policy.RequireRole("Admin"));

return app;
}
}

GitHub Actions CI/CD with Security Scanning

# .github/workflows/security-scan.yml
name: Security Scan

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 0 * * 0' # Weekly scan

jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Restore dependencies
run: dotnet restore
- name: Check for vulnerable packages
run: dotnet list package --vulnerable --include-transitive
- name: Check for outdated packages
run: dotnet list package --outdated
- name: Run security audit
run: dotnet build --configuration Release /p:NuGetAudit=true
- name: Run OWASP Dependency Check
uses: dependency-check/Dependency-Check_Action@main
with:
project: 'SecurityApp'
path: '.'
format: 'HTML'

Dependency Management Benefits:

  1. ✅ Automated vulnerability scanning
  2. ✅ NuGet audit during build
  3. ✅ Regular dependency updates
  4. ✅ CI/CD integration
  5. ✅ Health check endpoints

12. Addressing OWASP A08:2021 – Software and Data Integrity Failures

12.1 Problem Statement

Integrity failures include:

  1. Insecure CI/CD pipelines
  2. Auto-update without verification
  3. Serialization/deserialization vulnerabilities
  4. Unsigned or unverified code

12.2 Solution: Integrity Verification

Secure Data Transfer Objects with Validation

using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

namespace SecurityApp.API.Models;

public class SignedRequest<T> where T : class
{
public T Data { get; set; } = default!;
public string Signature { get; set; } = string.Empty;
public DateTime Timestamp { get; set; }

public bool VerifySignature(string secretKey)
{
// Prevent replay attacks - 5 minute window
if (Math.Abs((DateTime.UtcNow - Timestamp).TotalMinutes) > 5)
return false;

var dataJson = JsonSerializer.Serialize(Data);
var computedSignature = ComputeSignature(dataJson, Timestamp, secretKey);
return Signature == computedSignature;
}

public static SignedRequest<T> Create(T data, string secretKey)
{
var timestamp = DateTime.UtcNow;
var dataJson = JsonSerializer.Serialize(data);
var signature = ComputeSignature(dataJson, timestamp, secretKey);

return new SignedRequest<T>
{
Data = data,
Signature = signature,
Timestamp = timestamp
};
}

private static string ComputeSignature(string data, DateTime timestamp, string secretKey)
{
var message = $"{data}|{timestamp:O}";
var keyBytes = Encoding.UTF8.GetBytes(secretKey);
var messageBytes = Encoding.UTF8.GetBytes(message);
var hash = HMACSHA256.HashData(keyBytes, messageBytes);
return Convert.ToBase64String(hash);
}
}

Audit Trail for Data Changes

namespace SecurityApp.API.Models;

public class AuditLog
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string EntityName { get; set; } = string.Empty;
public string EntityId { get; set; } = string.Empty;
public string Action { get; set; } = string.Empty; // Create, Update, Delete
public string? OldValues { get; set; }
public string? NewValues { get; set; }
public string UserId { get; set; } = string.Empty;
public string UserEmail { get; set; } = string.Empty;
public string IpAddress { get; set; } = string.Empty;
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
}

// Audit interceptor for Entity Framework
public class AuditInterceptor : SaveChangesInterceptor
{
private readonly IHttpContextAccessor _httpContextAccessor;

public AuditInterceptor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}

public override InterceptionResult<int> SavingChanges(
DbContextEventData eventData,
InterceptionResult<int> result)
{
if (eventData.Context is ApplicationDbContext context)
{
AuditChanges(context);
}
return base.SavingChanges(eventData, result);
}

public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
if (eventData.Context is ApplicationDbContext context)
{
AuditChanges(context);
}
return base.SavingChangesAsync(eventData, result, cancellationToken);
}

private void AuditChanges(ApplicationDbContext context)
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null) return;

var userId = httpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "System";
var userEmail = httpContext.User.FindFirst(ClaimTypes.Email)?.Value ?? "Unknown";
var ipAddress = httpContext.Connection.RemoteIpAddress?.ToString() ?? "Unknown";

var entries = context.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added ||
e.State == EntityState.Modified ||
e.State == EntityState.Deleted);

foreach (var entry in entries)
{
var auditLog = new AuditLog
{
EntityName = entry.Entity.GetType().Name,
EntityId = entry.Properties.FirstOrDefault(p => p.Metadata.IsPrimaryKey())?.CurrentValue?.ToString() ?? "Unknown",
Action = entry.State.ToString(),
UserId = userId,
UserEmail = userEmail,
IpAddress = ipAddress,
Timestamp = DateTime.UtcNow
};

if (entry.State == EntityState.Modified)
{
var oldValues = entry.Properties
.Where(p => p.IsModified)
.ToDictionary(p => p.Metadata.Name, p => p.OriginalValue);
var newValues = entry.Properties
.Where(p => p.IsModified)
.ToDictionary(p => p.Metadata.Name, p => p.CurrentValue);

auditLog.OldValues = JsonSerializer.Serialize(oldValues);
auditLog.NewValues = JsonSerializer.Serialize(newValues);
}
else if (entry.State == EntityState.Added)
{
var newValues = entry.Properties
.ToDictionary(p => p.Metadata.Name, p => p.CurrentValue);
auditLog.NewValues = JsonSerializer.Serialize(newValues);
}
else if (entry.State == EntityState.Deleted)
{
var oldValues = entry.Properties
.ToDictionary(p => p.Metadata.Name, p => p.OriginalValue);
auditLog.OldValues = JsonSerializer.Serialize(oldValues);
}

context.Set<AuditLog>().Add(auditLog);
}
}
}

// Register in Program.cs
builder.Services.AddDbContext<ApplicationDbContext>((serviceProvider, options) =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
options.AddInterceptors(serviceProvider.GetRequiredService<AuditInterceptor>());
});

builder.Services.AddScoped<AuditInterceptor>();
builder.Services.AddHttpContextAccessor();

Data Integrity Benefits:

  1. ✅ HMAC signatures prevent tampering
  2. ✅ Timestamp validation prevents replay attacks
  3. ✅ Complete audit trail for all changes
  4. ✅ Automatic change tracking
  5. ✅ Forensic analysis capability

13. Addressing OWASP A10:2021 – Server-Side Request Forgery (SSRF)

13.1 Problem Statement

SSRF flaws occur when a web application fetches a remote resource without validating the user-supplied URL:

  1. Access to internal systems
  2. Port scanning
  3. Reading local files
  4. Bypassing firewalls

13.2 Solution: URL Validation and Whitelist

Secure HTTP Client Service

using System.Net;

namespace SecurityApp.API.Services;

public interface ISecureHttpClient
{
Task<HttpResponseMessage> GetAsync(string url);
Task<HttpResponseMessage> PostAsync(string url, HttpContent content);
}

public class SecureHttpClient : ISecureHttpClient
{
private readonly HttpClient _httpClient;
private readonly ILogger<SecureHttpClient> _logger;
private readonly IConfiguration _configuration;

// Blacklist for internal/private networks
private static readonly IPAddress[] BlockedNetworks = new[]
{
IPAddress.Parse("127.0.0.1"), // Localhost
IPAddress.Parse("0.0.0.0"), // Current network
IPAddress.Parse("10.0.0.0"), // Private network
IPAddress.Parse("172.16.0.0"), // Private network
IPAddress.Parse("192.168.0.0"), // Private network
IPAddress.Parse("169.254.0.0"), // Link-local
IPAddress.Parse("224.0.0.0") // Multicast
};

public SecureHttpClient(HttpClient httpClient, ILogger<SecureHttpClient> logger, IConfiguration configuration)
{
_httpClient = httpClient;
_logger = logger;
_configuration = configuration;
// Set timeout
_httpClient.Timeout = TimeSpan.FromSeconds(30);
}

public async Task<HttpResponseMessage> GetAsync(string url)
{
await ValidateUrlAsync(url);
return await _httpClient.GetAsync(url);
}

public async Task<HttpResponseMessage> PostAsync(string url, HttpContent content)
{
await ValidateUrlAsync(url);
return await _httpClient.PostAsync(url, content);
}

private async Task ValidateUrlAsync(string url)
{
// 1. Check if URL is well-formed
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
{
_logger.LogWarning("Invalid URL format: {Url}", url);
throw new SecurityException("Invalid URL format");
}

// 2. Only allow HTTPS
if (uri.Scheme != Uri.UriSchemeHttps)
{
_logger.LogWarning("Non-HTTPS URL rejected: {Url}", url);
throw new SecurityException("Only HTTPS URLs are allowed");
}

// 3. Check against whitelist
var allowedDomains = _configuration.GetSection("AllowedExternalDomains").Get<string[]>()
?? Array.Empty<string>();
if (!allowedDomains.Any(domain => uri.Host.EndsWith(domain, StringComparison.OrdinalIgnoreCase)))
{
_logger.LogWarning("Domain not in whitelist: {Domain}", uri.Host);
throw new SecurityException("Domain not allowed");
}

// 4. Resolve DNS and check IP
try
{
var addresses = await Dns.GetHostAddressesAsync(uri.Host);
foreach (var address in addresses)
{
// Block private/internal IPs
if (IsBlockedIpAddress(address))
{
_logger.LogWarning("Blocked IP address detected: {IP} for host {Host}",
address, uri.Host);
throw new SecurityException("Access to internal resources is not allowed");
}
}
}
catch (Exception ex) when (ex is not SecurityException)
{
_logger.LogError(ex, "DNS resolution failed for {Host}", uri.Host);
throw new SecurityException("Unable to resolve host");
}

// 5. Block non-standard ports
if (uri.Port != 443 && uri.Port != -1) // -1 means default port
{
_logger.LogWarning("Non-standard port rejected: {Port} for {Url}", uri.Port, url);
throw new SecurityException("Non-standard ports are not allowed");
}
}

private static bool IsBlockedIpAddress(IPAddress address)
{
// Check if loopback
if (IPAddress.IsLoopback(address))
return true;

// Check against blocked networks
var addressBytes = address.GetAddressBytes();
foreach (var blockedNetwork in BlockedNetworks)
{
var blockedBytes = blockedNetwork.GetAddressBytes();
// Simple prefix check (first octet for most cases)
if (addressBytes.Length == blockedBytes.Length &&
addressBytes[0] == blockedBytes[0])
{
// More detailed check for private networks
if (addressBytes[0] == 10) return true;
if (addressBytes[0] == 172 && addressBytes[1] >= 16 && addressBytes[1] <= 31) return true;
if (addressBytes[0] == 192 && addressBytes[1] == 168) return true;
if (addressBytes[0] == 127) return true;
}
}

return false;
}
}

public class SecurityException : Exception
{
public SecurityException(string message) : base(message) { }
}

SSRF Protection Configuration

// appsettings.json
{
"AllowedExternalDomains": [
"api.github.com",
"api.stripe.com",
"api.sendgrid.com"
]
}

Usage Example with Protected Endpoint

namespace SecurityApp.API.Endpoints;

public static class WebhookEndpoints
{
public static IEndpointRouteBuilder MapWebhookEndpoints(this IEndpointRouteBuilder app)
{
app.MapPost("/api/webhook/register", async (
[FromBody] WebhookRegistrationRequest request,
ISecureHttpClient httpClient) =>
{
try
{
// Validate webhook URL before saving
var testResponse = await httpClient.PostAsync(
request.WebhookUrl,
JsonContent.Create(new { test = true }));

if (testResponse.IsSuccessStatusCode)
{
// Save webhook configuration
return Results.Ok(new { Message = "Webhook registered successfully" });
}

return Results.BadRequest(new { Message = "Webhook URL validation failed" });
}
catch (SecurityException ex)
{
return Results.BadRequest(new { Message = ex.Message });
}
})
.RequireAuthorization()
.WithName("RegisterWebhook");

return app;
}
}

public record WebhookRegistrationRequest(string WebhookUrl, string EventType);

SSRF Protection Benefits:

  1. ✅ Whitelist of allowed domains
  2. ✅ DNS resolution with IP blocking
  3. ✅ Prevention of access to internal networks
  4. ✅ HTTPS-only enforcement
  5. ✅ Port restriction
  6. ✅ Comprehensive logging

14. Complete Middleware Pipeline Configuration

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference();
}

app.UseHttpsRedirection();

// 1. JWT Validation Details (Logging)
app.UseMiddleware<JwtValidationDetailsMiddleware>();

// 2. Authentication & Authorization
app.UseAuthentication();
app.UseAuthorization();

// 3. IP Validation (Access Control)
app.UseMiddleware<IpValidationMiddleware>();

// 4. User-Agent Validation (Access Control)
app.UseMiddleware<UserAgentValidationMiddleware>();

// 5. Map Endpoints
app.MapAuthEndpoints();

app.Run();

14. Complete Middleware Pipeline Configuration

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference();
}

app.UseHttpsRedirection();

// 1. Security Headers (A05 - Security Misconfiguration)
app.UseMiddleware<SecurityHeadersMiddleware>();

// 2. Injection Protection (A03 - Injection)
app.UseMiddleware<InjectionProtectionMiddleware>();

// 3. Rate Limiting (A05 - Security Misconfiguration)
app.UseRateLimiter();

// 4. CORS (A05 - Security Misconfiguration)
app.UseCors("SecurePolicy");

// 5. JWT Validation Details (A09 - Logging)
app.UseMiddleware<JwtValidationDetailsMiddleware>();

// 6. Authentication (A07 - Auth Failures)
app.UseAuthentication();

// 7. Authorization (A01 - Broken Access Control)
app.UseAuthorization();

// 8. IP Validation (A01 - Broken Access Control, A07 - Auth Failures)
app.UseMiddleware<IpValidationMiddleware>();

// 9. User-Agent Validation (A01 - Broken Access Control, A07 - Auth Failures)
app.UseMiddleware<UserAgentValidationMiddleware>();

// 10. Map Endpoints
app.MapAuthEndpoints();
app.MapHealthEndpoints();
app.MapWebhookEndpoints();

app.Run();

Complete Pipeline Addresses:

  1. A01: Access control validation at multiple layers
  2. A02: Cryptographic hashing and JWT signing
  3. A03: Injection pattern detection
  4. A04: Secure design with repository pattern
  5. A05: Security headers and configuration
  6. A06: Dependency scanning and health checks
  7. A07: Token fingerprinting and strong auth
  8. A08: Data integrity with audit trails
  9. A09: Comprehensive logging throughout
  10. A10: SSRF prevention with URL validation

15. Security Benefits Summary

Protection Against Multiple Attack Vectors

Attack TypeWithout ImplementationWith ImplementationOWASP Category
Token Theft✗ Attacker can use stolen token✓ Token rejected (IP/UA mismatch)A07, A01
Replay Attack✗ Old tokens can be reused✓ Detected via expiration & fingerprintA07
MITM Attack✗ Token captured and reused✓ IP binding prevents remote useA07, A02
XSS Token Extraction✗ Stolen token works anywhere✓ Limited to original environmentA07
SQL Injection✗ Database compromise✓ Parameterized queries block injectionA03
SSRF Attack✗ Access internal systems✓ URL whitelist blocks internal accessA10
Brute Force✗ No account lockout✓ 5 attempts → 5 min lockoutA07
Weak Passwords✗ Simple passwords allowed✓ Enforced complexity rulesA07
Unauthorized Access✗ No fine-grained control✓ Multi-layer authorizationA01
Data Tampering✗ No integrity checks✓ HMAC signatures & audit trailsA08
Information Disclosure✗ Verbose errors✓ Generic error messagesA05
Vulnerable Dependencies✗ No tracking✓ Automated scanning & updatesA06

Compliance with OWASP Top 10 2021

OWASP CategoryImplementationKey FeaturesStatus
A01: Broken Access ControlIP/UA validation, authorization checks, repository patternToken binding, role checks, ownership validation✅ Addressed
A02: Cryptographic FailuresSHA256 hashing, HMAC-SHA256 signingStrong hashing, secure JWT config, HTTPS enforcement✅ Addressed
A03: InjectionParameterized queries, input validation, pattern detectionEF Core protection, validation attributes, middleware✅ Addressed
A04: Insecure DesignDomain-driven design, secure patternsImmutable models, factory methods, built-in security✅ Addressed
A05: Security MisconfigurationSecurity headers, secure defaults, strict validationCORS, HSTS, CSP, rate limiting, no detailed errors✅ Addressed
A06: Vulnerable ComponentsNuGet audit, dependency scanning, health checksAutomated scanning, CI/CD integration, version tracking✅ Addressed
A07: Auth FailuresToken fingerprinting, strong passwords, lockoutIP/UA binding, complexity rules, account lockout✅ Addressed
A08: Data Integrity FailuresHMAC signatures, audit trails, change trackingRequest signing, complete audit log, EF interceptors✅ Addressed
A09: Logging FailuresComprehensive logging & monitoringJWT validation logs, audit trails, security events✅ Addressed
A10: SSRFURL validation, whitelist, IP blockingDomain whitelist, DNS resolution, internal IP blocking✅ Addressed

16. Best Practices and Recommendations

16.1 Token Expiration Strategy

Access Token: 60 minutes (short-lived)
Refresh Token: 7 days (long-lived, stored server-side)

Rationale:

  1. Short access token lifetime limits exposure window
  2. Refresh tokens enable seamless user experience
  3. Server-side refresh token storage enables revocation

16.2 Production Considerations

For IP Binding:

  1. ⚠️ Mobile Users: IP changes when switching WiFi ↔ Cellular
  2. 💡 Solution: Make IP validation optional or use partial IP matching
  3. 💡 Alternative: Use geolocation + IP subnet validation

For User-Agent Binding:

  1. ⚠️ Browser Updates: User-Agent changes with browser version
  2. 💡 Solution: Hash only major version or browser family
  3. 💡 Alternative: Use device fingerprinting libraries

Logging in Production:

// Use structured logging with correlation IDs
logger.LogInformation(
"Token validation - RequestId: {RequestId}, User: {UserId}, " +
"IP: {ClientIP}, Result: {Result}",
context.TraceIdentifier,
userId,
ipHash,
"Success");

16.3 Performance Optimization

// Cache SHA256 computation for same values within request
public class CachedHashService
{
private readonly IMemoryCache _cache;
public string GetOrComputeHash(string input, string cacheKey)
{
return _cache.GetOrCreate(cacheKey, entry =>
{
entry.SlidingExpiration = TimeSpan.FromMinutes(5);
return ComputeSha256Hash(input);
});
}
}

16.4 Monitoring and Alerting

Set up alerts for:

  1. High rate of IP/UA mismatches (potential attack)
  2. Multiple failed login attempts from same IP
  3. Token expiration spikes (possible time-based attack)
  4. Unusual token validation failure patterns

17. Testing Recommendations

17.1 Unit Tests

[Fact]
public void ComputeSha256Hash_SameInput_ProducesSameHash()
{
var input = "192.168.1.1";
var hash1 = HttpContextExtensions.ComputeSha256Hash(input);
var hash2 = HttpContextExtensions.ComputeSha256Hash(input);
Assert.Equal(hash1, hash2);
}

[Fact]
public void ComputeSha256Hash_DifferentInput_ProducesDifferentHash()
{
var hash1 = HttpContextExtensions.ComputeSha256Hash("192.168.1.1");
var hash2 = HttpContextExtensions.ComputeSha256Hash("192.168.1.2");
Assert.NotEqual(hash1, hash2);
}

17.2 Integration Tests

[Fact]
public async Task Login_WithDifferentIP_ShouldRejectAccess()
{
// Arrange: Login from IP1
var loginResponse = await LoginWithIP("192.168.1.1");
var token = loginResponse.AccessToken;
// Act: Try to access from IP2
var result = await AccessProtectedResourceWithIP(token, "192.168.1.2");
// Assert
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
}

[Fact]
public async Task SqlInjection_ShouldBeBlocked()
{
// Arrange
var maliciousInput = "admin' OR '1'='1";
// Act
var result = await LoginWithEmail(maliciousInput);
// Assert
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
}

[Fact]
public async Task SSRF_InternalIP_ShouldBeBlocked()
{
// Arrange
var internalUrl = "http://192.168.1.1/admin";
// Act & Assert
await Assert.ThrowsAsync<SecurityException>(async () =>
{
await _secureHttpClient.GetAsync(internalUrl);
});
}

17.3 Security Testing

[Fact]
public async Task WeakPassword_ShouldBeRejected()
{
// Arrange
var request = new RegisterRequest
{
Email = "test@example.com",
Password = "weak", // Too short, no complexity
FirstName = "Test",
LastName = "User"
};
// Act
var result = await RegisterUser(request);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
}

[Fact]
public async Task RateLimiting_ShouldBlockExcessiveRequests()
{
// Arrange: Make 101 requests (limit is 100/min)
var tasks = Enumerable.Range(0, 101)
.Select(_ => MakeAuthenticatedRequest());
// Act
var results = await Task.WhenAll(tasks);
// Assert
var tooManyRequests = results.Count(r => r.StatusCode == HttpStatusCode.TooManyRequests);
Assert.True(tooManyRequests > 0);
}

18. Conclusion

This implementation demonstrates a comprehensive approach to addressing multiple OWASP Top 10 security vulnerabilities through JWT token fingerprinting and enhanced security mechanisms. By binding tokens to specific client environments, implementing strong cryptographic practices, and maintaining detailed security logging, we significantly reduce the attack surface of web applications.

Key Takeaways

  1. Defense in Depth: Multiple security layers provide better protection
  2. Token Fingerprinting: Simple yet effective against token theft
  3. Hashing > Plain Text: Always hash sensitive binding data
  4. Log Everything: Security logging is crucial for threat detection
  5. Think Mobile: Consider mobile user experience in security design

18. Conclusion

This comprehensive implementation demonstrates practical solutions for all OWASP Top 10 2021 security vulnerabilities in an ASP.NET Core application. Through JWT token fingerprinting, secure design patterns, and defense-in-depth strategies, we've created a robust security framework that significantly reduces the attack surface.

Key Takeaways

  1. Defense in Depth: Multiple security layers provide better protection than single solutions
  2. Token Fingerprinting: Simple yet highly effective against token theft and session hijacking
  3. Hashing > Plain Text: Always hash sensitive binding data (IP, User-Agent) for privacy
  4. Input Validation Everywhere: Validate and sanitize at multiple layers (client, middleware, service, database)
  5. Secure by Default: Build security into design, not as an afterthought
  6. Log Everything Security-Related: Comprehensive logging enables threat detection and forensics
  7. Keep Dependencies Updated: Regular scanning and updates prevent exploitation of known vulnerabilities
  8. Think Mobile: Consider mobile user experience (IP changes, browser updates) in security design
  9. Audit Data Changes: Complete audit trails enable accountability and investigation
  10. Whitelist > Blacklist: Use whitelists for allowed domains, IPs, and patterns

Real-World Impact

Before Implementation:

  1. ❌ Stolen tokens usable from anywhere
  2. ❌ No protection against SQL injection
  3. ❌ Internal systems accessible via SSRF
  4. ❌ No audit trail for data changes
  5. ❌ Verbose error messages expose system details
  6. ❌ Weak passwords accepted

After Implementation:

  1. ✅ Tokens bound to specific device/IP
  2. ✅ Parameterized queries block injection
  3. ✅ Whitelist prevents SSRF attacks
  4. ✅ Complete audit trail for accountability
  5. ✅ Generic error messages in production
  6. ✅ Strong password requirements enforced

Future Enhancements

  1. Implement advanced device fingerprinting (FingerprintJS, Canvas)
  2. Add machine learning for anomaly detection
  3. Integrate with SIEM systems (Splunk, ELK)
  4. Implement multi-factor authentication (MFA/2FA)
  5. Add hardware security keys support (WebAuthn/FIDO2)
  6. Deploy rate limiting with DDoS protection
  7. Implement zero trust architecture
Share this lesson: