Articles › Security
Web Application Security Essentials: OWASP Top 10 for .NET and React Developers
Protect your apps from the most common attacks: SQL injection, XSS, CSRF, broken access control, IDOR, insecure secrets and misconfiguration — with practical .NET and React fixes.
Security isn't a feature you add at the end — it's a set of habits you apply in every line of code. Most real-world breaches don't use exotic exploits; they abuse ordinary mistakes: a query built with string concatenation, an endpoint that forgets to check ownership, a secret committed to Git. This guide walks through the most common risks from the OWASP Top 10 and shows concrete defenses for ASP.NET Core and React.
1. Broken access control (and IDOR)
The #1 risk in modern applications. A user can access data or actions they shouldn't.
// ❌ Any logged-in user can read any order by changing the id
[Authorize]
[HttpGet("orders/{id:int}")]
public async Task<IActionResult> Get(int id) =>
Ok(await db.Orders.FindAsync(id));
This is an IDOR (Insecure Direct Object Reference). Authentication isn't authorization — check ownership:
// ✅ Scope every query to the current user
[Authorize]
[HttpGet("orders/{id:int}")]
public async Task<IActionResult> Get(int id)
{
var userId = User.GetUserId();
var order = await db.Orders
.Where(o => o.Id == id && o.CustomerId == userId)
.Select(o => new OrderDto(o.Id, o.Total, o.Status))
.FirstOrDefaultAsync();
return order is null ? NotFound() : Ok(order);
}
Rules:
- Deny by default; protect controllers with
[Authorize]and open only what must be public. - Enforce authorization on the server — hiding a button in React protects nothing.
- Check permissions for every action, including updates and deletes.
2. Injection (SQL injection)
// ❌ Never build SQL with user input
var sql = $"SELECT * FROM Users WHERE Email = '{email}'";
// email = "' OR 1=1 --" → returns every user
Use parameters — always:
// ✅ EF Core LINQ is parameterized
var user = await db.Users.FirstOrDefaultAsync(u => u.Email == email);
// ✅ Raw SQL with interpolation is parameterized by FromSql
var users = await db.Users.FromSql($"SELECT * FROM Users WHERE Email = {email}").ToListAsync();
// ✅ Dapper
var u = await conn.QuerySingleOrDefaultAsync<User>(
"SELECT * FROM Users WHERE Email = @Email", new { Email = email });
Warning:
FromSqlRawwith string concatenation is not safe. Only interpolatedFromSql/ExecuteSql(or explicitSqlParameters) are parameterized.
The same principle applies to OS commands, LDAP queries and dynamic ORDER BY columns — whitelist allowed values instead of inserting input.
3. Cross-Site Scripting (XSS)
XSS happens when attacker-controlled text is rendered as HTML/JavaScript in another user's browser — stealing tokens or performing actions as the victim.
React escapes values in JSX by default:
<p>{comment.text}</p> // ✅ rendered as text, even if it contains <script>
The danger zones:
// ❌ Raw HTML from users
<div dangerouslySetInnerHTML={{ __html: comment.html }} />
// ✅ Sanitize first
import DOMPurify from "dompurify";
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(comment.html) }} />
// ❌ User-controlled URLs can be "javascript:..."
<a href={profile.website}>Website</a>
Validate URLs (allow only http:/https:) and sanitize rich text on the server too (e.g. the HtmlSanitizer NuGet package). Add a Content Security Policy header to limit where scripts can load from:
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'
4. Cross-Site Request Forgery (CSRF)
If authentication uses cookies, another site can trigger requests that the browser sends with the user's cookies. Defenses:
SameSite=LaxorStrictcookies (the modern default in browsers).- Anti-forgery tokens for cookie-authenticated forms (
[ValidateAntiForgeryToken]in MVC, built-in for Razor Pages). - Never change state with
GETrequests.
APIs that use Authorization: Bearer headers aren't vulnerable to classic CSRF, because browsers don't attach those headers automatically.
5. Cryptographic failures
- HTTPS everywhere, with HSTS (
app.UseHsts()). - Hash passwords with slow, salted algorithms: ASP.NET Core Identity's
PasswordHasher, PBKDF2, BCrypt or Argon2. Never MD5/SHA1, never reversible encryption. - Encrypt sensitive data at rest when required (e.g. national IDs), and protect keys with a key management service or ASP.NET Core Data Protection.
- Generate tokens with
RandomNumberGenerator, notRandom.
6. Security misconfiguration
- Don't show the developer exception page in production; return generic errors with a
traceId. - Disable Swagger UI in production or protect it.
- Remove default accounts and sample endpoints.
- Restrict CORS to known origins:
builder.Services.AddCors(o => o.AddPolicy("web", p => p
.WithOrigins("https://alimahmoodi.ir")
.AllowAnyHeader()
.AllowAnyMethod()));
- Add security headers:
X-Content-Type-Options: nosniff,Referrer-Policy,frame-ancestors(clickjacking protection).
7. Secrets management
❌ appsettings.json committed with real connection strings and API keys
❌ API keys inside React code (anything in the bundle is public!)
- Development:
dotnet user-secrets. - Production: environment variables or a vault (Azure Key Vault, HashiCorp Vault).
- Frontend: never ship secrets; call your own backend, which holds the key.
- If a secret leaks, rotate it — removing it from Git history isn't enough.
8. Vulnerable dependencies
dotnet list package --vulnerable --include-transitive
npm audit
Keep frameworks and packages updated, enable Dependabot or Renovate, and remove packages you don't use.
9. Authentication weaknesses
- Rate-limit login, OTP and password-reset endpoints to stop brute force.
- Lock out or slow down after repeated failures.
- Use short-lived access tokens and rotate refresh tokens.
- Support MFA for admin accounts.
- Return the same message for "unknown user" and "wrong password".
10. File upload risks
// ✅ Defensive upload handling
var allowed = new[] { ".jpg", ".jpeg", ".png", ".webp" };
var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!allowed.Contains(ext) || file.Length > 5 * 1024 * 1024)
return BadRequest("Invalid file.");
var safeName = $"{Guid.NewGuid():N}{ext}"; // never trust the client's file name
var path = Path.Combine(uploadsRoot, safeName); // outside of executable folders
Validate size and type (ideally by inspecting content, not just extension), store files outside executable paths, serve them with correct Content-Type, and consider re-encoding images to strip malicious payloads.
Logging and monitoring
You can't respond to attacks you can't see. Log authentication failures, authorization denials and validation errors — but never log passwords, tokens or full card numbers. Set alerts for unusual spikes.
A security checklist
- Every endpoint has explicit authorization and ownership checks
- All database access is parameterized
- No raw HTML rendering without sanitization; CSP configured
- HTTPS + HSTS; secure,
HttpOnly,SameSitecookies - Passwords hashed with a slow algorithm
- Secrets in a vault/env vars, never in Git or frontend code
- CORS restricted; production error pages generic
- Rate limiting on auth endpoints
- Dependencies scanned regularly
- Uploads validated and stored safely
Summary
Most vulnerabilities come from trusting input and forgetting authorization. Parameterize every query, encode and sanitize output, check ownership on the server for every resource, keep secrets out of code, configure HTTPS, CORS and headers carefully, and keep dependencies updated. Make these habits part of your daily coding and code reviews, and your applications will resist the vast majority of real-world attacks.
FAQ
Is React automatically safe from XSS?
Mostly, because JSX escapes values. The risks are dangerouslySetInnerHTML, user-controlled URLs and third-party scripts.
Does using EF Core prevent SQL injection?
LINQ queries and interpolated FromSql are parameterized. Concatenating strings into FromSqlRaw or ExecuteSqlRaw reintroduces the risk.
Where should I start securing an existing app?
Begin with access control (ownership checks on every endpoint), secrets management and dependency updates — they give the biggest risk reduction fastest.