SQL Injection is a code injection attack where malicious SQL statements are inserted into application queries, allowing attackers to manipulate database operations, access unauthorized data, or even destroy data.
Example of Vulnerable Code:
// VULNERABLE - Never do this!
string query = $"SELECT * FROM Users WHERE Username = '{username}' AND Password = '{password}'";
var result = context.Users.FromSqlRaw(query).ToList();
Prevention in .NET Core:
- Use Parameterized Queries (Preferred Method):
var result = context.Users
.FromSqlRaw("SELECT * FROM Users WHERE Username = {0} AND Password = {1}", username, password)
.ToList();
- Use LINQ and Entity Framework Core:
var user = context.Users
.Where(u => u.Username == username && u.Password == password)
.FirstOrDefault();
- Use Stored Procedures:
var user = context.Users
.FromSqlRaw("EXEC GetUserByCredentials @Username, @Password",
new SqlParameter("@Username", username),
new SqlParameter("@Password", password))
.FirstOrDefault();
- Input Validation:
public class LoginModel
{
[Required]
[StringLength(50, MinimumLength = 3)]
[RegularExpression(@"^[a-zA-Z0-9_]+$")]
public string Username { get; set; }
}
Related Resources
Cross-Site Scripting (XSS)
XSS allows attackers to inject malicious scripts into web pages viewed by other users, stealing cookies, session tokens, or other sensitive information.
Types of XSS:
- Stored XSS: Malicious script stored in database
- Reflected XSS: Script reflected off web server
- DOM-based XSS: Vulnerability in client-side code
Prevention in .NET Core:
- Use Razor Encoding (Automatic):
@Model.UserInput // Automatically HTML encoded
- For Raw HTML (Use with Caution):
@Html.Raw(Model.TrustedContent) // Only for trusted content
- Content Security Policy:
app.Use(async (context, next) =>
{
context.Response.Headers.Add("Content-Security-Policy",
"default-src 'self'; script-src 'self' 'unsafe-inline'");
await next();
});
- Anti-XSS Library:
using Microsoft.Security.Application;
string safe = Encoder.HtmlEncode(userInput);
Cross-Site Request Forgery (CSRF)
CSRF forces authenticated users to execute unwanted actions on a web application by exploiting their active session.
Prevention in .NET Core:
- Anti-Forgery Tokens (Built-in):
// In Startup.cs
services.AddControllersWithViews(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
// In Razor view
@Html.AntiForgeryToken()
// In Controller
[ValidateAntiForgeryToken]
public IActionResult SubmitForm(FormModel model)
{
// Process form
}
- For AJAX Requests:
// In _Layout.cshtml
// JavaScript
var token = document.querySelector('meta[name="csrf-token"]').content;
fetch('/api/data', {
method: 'POST',
headers: {
'RequestVerificationToken': token
}
});
- SameSite Cookies:
services.ConfigureApplicationCookie(options =>
{
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
Related Resources
Never store passwords in plain text! Always use cryptographic hashing with salting.
Best Practices in .NET Core:
- Use ASP.NET Core Identity (Recommended):
// Startup.cs
services.AddIdentity(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 12;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
})
.AddEntityFrameworkStores();
// Usage
public class AccountController : Controller
{
private readonly UserManager _userManager;
public async Task Register(RegisterModel model)
{
var user = new ApplicationUser { UserName = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
}
}
- Manual Implementation with BCrypt:
using BCrypt.Net;
public class PasswordHasher
{
public string HashPassword(string password)
{
return BCrypt.HashPassword(password, BCrypt.GenerateSalt(12));
}
public bool VerifyPassword(string password, string hashedPassword)
{
return BCrypt.Verify(password, hashedPassword);
}
}
- Using PBKDF2 (Built-in .NET):
using System.Security.Cryptography;
public class PasswordHasher
{
private const int SaltSize = 16;
private const int HashSize = 20;
private const int Iterations = 100000;
public string HashPassword(string password)
{
byte[] salt = new byte[SaltSize];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(salt);
}
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, Iterations, HashAlgorithmName.SHA256);
byte[] hash = pbkdf2.GetBytes(HashSize);
byte[] hashBytes = new byte[SaltSize + HashSize];
Array.Copy(salt, 0, hashBytes, 0, SaltSize);
Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);
return Convert.ToBase64String(hashBytes);
}
public bool VerifyPassword(string password, string hashedPassword)
{
byte[] hashBytes = Convert.FromBase64String(hashedPassword);
byte[] salt = new byte[SaltSize];
Array.Copy(hashBytes, 0, salt, 0, SaltSize);
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, Iterations, HashAlgorithmName.SHA256);
byte[] hash = pbkdf2.GetBytes(HashSize);
for (int i = 0; i < HashSize; i++)
{
if (hashBytes[i + SaltSize] != hash[i])
return false;
}
return true;
}
}
Key Principles:
- Use adaptive hashing algorithms (BCrypt, Argon2, PBKDF2)
- Always use unique salts per password
- Use sufficient iteration counts (work factor)
- Never store passwords reversibly
Related Resources
OAuth 2.0 provides authorization, while OpenID Connect adds authentication on top of OAuth 2.0.
Implementation in .NET Core:
- Install Required Packages:
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
- Configure Authentication (Startup.cs):
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = "https://your-identity-provider.com";
options.ClientId = "your-client-id";
options.ClientSecret = "your-client-secret";
options.ResponseType = "code";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true
};
});
}
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseAuthorization();
}
- Protecting Endpoints:
[Authorize]
public class SecureController : Controller
{
public IActionResult Index()
{
var userName = User.Identity.Name;
var claims = User.Claims;
return View();
}
}
- API Authentication with JWT:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://your-identity-provider.com";
options.Audience = "your-api-resource";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true
};
});
- Using Identity Server (Self-hosted):
// Install: dotnet add package IdentityServer4
services.AddIdentityServer()
.AddInMemoryClients(Config.Clients)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddDeveloperSigningCredential();
- Calling Protected APIs:
public class ApiClient
{
private readonly HttpClient _httpClient;
private readonly IHttpContextAccessor _httpContextAccessor;
public async Task GetDataAsync()
{
var accessToken = await _httpContextAccessor.HttpContext
.GetTokenAsync("access_token");
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
var response = await _httpClient.GetAsync("https://api.example.com/data");
return await response.Content.ReadAsStringAsync();
}
}
Related Resources
Principle of Least Privilege means granting users, processes, or systems only the minimum permissions necessary to perform their functions.
Implementation in .NET Core:
- Role-Based Access Control:
// Define roles in Startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Administrator"));
options.AddPolicy("UserOrAdmin", policy =>
policy.RequireRole("User", "Administrator"));
});
// Use in controllers
[Authorize(Roles = "Administrator")]
public class AdminController : Controller
{
// Only administrators can access
}
[Authorize(Policy = "AdminOnly")]
public IActionResult DeleteUser(int id)
{
// Sensitive operation
}
- Claims-Based Authorization:
services.AddAuthorization(options =>
{
options.AddPolicy("CanEditDocuments", policy =>
policy.RequireClaim("Permission", "Document.Edit"));
options.AddPolicy("SeniorEmployees", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(c => c.Type == "EmployeeLevel"
&& int.Parse(c.Value) >= 5)));
});
[Authorize(Policy = "CanEditDocuments")]
public IActionResult EditDocument(int id)
{
// Only users with Document.Edit claim
}
- Resource-Based Authorization:
public class DocumentAuthorizationHandler :
AuthorizationHandler
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Document resource)
{
if (resource.OwnerId == context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
// Usage in controller
public class DocumentController : Controller
{
private readonly IAuthorizationService _authorizationService;
public async Task Edit(int id)
{
var document = await _repository.GetAsync(id);
var authResult = await _authorizationService.AuthorizeAsync(
User, document, "EditPolicy");
if (!authResult.Succeeded)
return Forbid();
return View(document);
}
}
- Database-Level Permissions:
// Connection string with limited permissions
"Server=myserver;Database=mydb;User Id=app_user;Password=***;"
// User 'app_user' should only have:
// - SELECT, INSERT, UPDATE on specific tables
// - EXECUTE on specific stored procedures
// - NO DROP, ALTER, or admin privileges
- API Key Scoping:
public class ApiKeyAuthorizationHandler : AuthorizationHandler
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
ApiKeyRequirement requirement)
{
var apiKey = context.User.FindFirst("ApiKey")?.Value;
var scopes = GetApiKeyScopes(apiKey);
if (scopes.Contains(requirement.RequiredScope))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}