Build the application around parameterized MySQL queries, safe React rendering, explicit CSRF protection for cookie-authenticated requests, and server-side authorization on every protected resource; add Azure WAF only as a production defense-in-depth layer.
1. Prevent SQL injection
- Use prepared statements or parameterized queries for every value received from forms, URLs, headers, or APIs. Parameters keep external values separate from SQL syntax.^1^
- Never construct SQL with string concatenation or template literals.
- Validate type, length, format, and range on the Express server. React validation improves usability but can be bypassed.^2^
- SQL parameters cannot represent identifiers such as column names or
ASC/DESC. Map those values through a fixed allowlist.
- Give the application's MySQL account only the required permissions—normally no schema-administration or global privileges.
Example using a MySQL driver that supports placeholders:
app.post('/api/profile', async (req, res, next) => {
try {
const email = String(req.body.email ?? '').trim();
if (email.length < 3 || email.length > 254) {
return res.status(400).json({ error: 'Invalid email' });
}
const [rows] = await pool.execute(
'SELECT id, email FROM users WHERE email = ? LIMIT 1',
[email]
);
res.json(rows[0] ?? null);
} catch (error) {
next(error);
}
});
Return a generic error to clients and log an internal correlation ID—not SQL text, connection strings, passwords, or sensitive parameter values. Detailed database exceptions can expose information useful for probing attacks.^1^
2. Prevent XSS
Treat form fields, query parameters, headers, API responses, and database content as untrusted.
- Render user content as text with normal React interpolation, such as
<p>{comment}</p>.
- Do not build markup with
innerHTML, document.write(), or untrusted string concatenation.
- Avoid
dangerouslySetInnerHTML. If rich HTML is a requirement, sanitize it with a maintained HTML sanitizer using a restrictive allowlist before rendering.
- Encode for the destination context: HTML text, HTML attribute, URL, and JavaScript contexts require different handling. Input validation alone does not replace output encoding.^3^
- Add a Content Security Policy (CSP) that limits permitted script, style, image, frame, and connection sources. Avoid
unsafe-inline and unsafe-eval; they significantly weaken CSP.^4^
- Add standard security headers through maintained Express middleware such as Helmet, then test the resulting CSP against the production React build.
CSP is a secondary containment layer, not a substitute for safe rendering and sanitization. It controls which resources a page may load or execute and helps limit XSS and clickjacking.^5^
3. Prevent CSRF
The correct design depends on how authentication is transported:
- Session/authentication cookie: require an unpredictable anti-CSRF token on every state-changing
POST, PUT, PATCH, and DELETE; validate it server-side. Set the session cookie to Secure, HttpOnly, and preferably SameSite=Lax or Strict.
- Cross-site application requiring
SameSite=None: the cookie must also be Secure, and anti-CSRF token validation remains necessary.
- Never change server state through
GET.
- Check
Origin or Fetch Metadata headers as an additional control, not as the only defense.
- Restrict CORS to known frontend origins. CORS is not CSRF protection.
Browsers automatically attach cookies to eligible requests, which creates the CSRF risk. SameSite mitigates many attacks but should be combined with anti-forgery protection for cookie-authenticated applications.^6^
Bearer [Token] manually placed in an Authorization header are less exposed to traditional CSRF, but browser storage accessible to JavaScript can be stolen through XSS. Do not choose token storage solely to avoid implementing CSRF defenses.
4. Prevent unauthorized data access
Authentication identifies the caller; authorization determines whether that caller may perform the requested operation.
- Apply authentication middleware to every protected API route.
- Authorize each operation server-side by role, permission, tenant, and resource ownership.
- Query resources using both the requested identifier and the authorized owner/tenant identifier. Do not fetch by ID and assume that possession of the ID grants access.
- Default to deny, return
403 for authenticated-but-forbidden requests, and avoid exposing whether another user's resource exists.
- Hash passwords with a vetted adaptive password-hashing library; never encrypt or store plaintext passwords.
- Rate-limit login, password-reset, registration, and other abuse-sensitive endpoints.
- Rotate sessions after login or privilege changes, expire them appropriately, and invalidate them at logout.
- Keep production database credentials and signing secrets out of source control and client-side React code.
If hosted in Azure, store production secrets in Azure Key Vault and grant the workload only the minimum required RBAC permissions. Managed identities remove embedded credentials when the target Azure service supports them.^7^
5. Production hardening
- Enforce HTTPS and secure cookies.
- Set strict request-body size limits and accepted content types.
- Keep Node.js and dependencies patched; run dependency and static-analysis checks in CI.
- Avoid logging passwords, tokens, cookies, authorization headers, or personal data.
- Record authentication failures, authorization denials, administrative actions, and suspicious request rates.
- Test access-control boundaries and malicious inputs in automated integration tests.
For a public Azure-hosted application, place Azure WAF on Azure Front Door or Application Gateway in front of it. WAF can detect HTTP-layer attacks including SQL injection and XSS, but it does not replace parameterized queries, encoding, authorization, or CSRF controls.^8^ Tune rules with representative traffic, switch production to Prevention mode, send diagnostics to Log Analytics, scrub sensitive log fields, and prevent clients from bypassing Front Door by reaching the origin directly.^8^
Use the OWASP Node.js, SQL injection, XSS, and CSRF cheat sheets as the implementation checklist for code review and release gates.^9^
References
- Secure data access
- SQL injection
- Prevent Cross-Site Scripting (XSS) in ASP.NET Core
- Unsafe-inline and unsafe-eval - Microsoft Q&A
- Glossary
- Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core
- Secure your Azure Key Vault
- Web Application Firewall for Azure networks (cross-cloud)
- Introduction - OWASP Cheat Sheet Series