OWASP Top 10:2025: What Changed
The OWASP Top 10 was updated in early 2025, and the changes reflect the reality of modern web application development. Two entries stand out as particularly relevant for full-stack teams:
A04:2025 – Cryptographic Failures now covers not just data-in-transit and data-at-rest, but also data-in-use. With the rise of client-side processing, API tokens stored in browser memory, and WebAssembly modules handling sensitive computations, the scope of "cryptographic failure" has expanded significantly.
A10:2025 – Server-Side Request Forgery (SSRF) was elevated from its previous position because of the prevalence of microservice architectures where internal services make requests to other internal services. When those requests are influenced by user input, SSRF becomes a critical attack vector.
The rest of the top 10 remains familiar: Broken Access Control tops the list, followed by Security Misconfiguration, Vulnerable and Outdated Components, Identification and Authentication Failures, Software and Data Integrity Failures, Security Logging and Monitoring Failures, and Injection.
Frontend Security: The First Line of Defense
Content Security Policy (CSP)
A strong CSP is the single most effective defense against XSS attacks in modern browsers. But most teams either skip it entirely or deploy a policy so permissive it provides no protection.
A practical CSP for a React/Next.js application:
default-src: 'self' – Block all resource loading by default.script-src: 'self' 'nonce-{random}' – Only allow scripts from your origin with a cryptographic nonce. Never use 'unsafe-inline' for scripts.style-src: 'self' 'unsafe-inline' – Styles are lower risk than scripts, but prefer nonce-based loading where possible.img-src: 'self' https: data: – Allow images from your origin and HTTPS sources.connect-src: 'self' https://your-api.com – Only allow fetch/XHR to your own API.frame-ancestors: 'none' – Prevent embedding in iframes (clickjacking protection).Generate nonces per request. In Next.js, use middleware to inject a nonce into the response headers and pass it to your layout via a server component. This is more secure than a static nonce because it changes with every request.
Trusted Types
Trusted Types is a browser API that prevents DOM XSS by requiring that all dangerous sink operations (innerHTML, document.write, eval) receive a TrustedHTML, TrustedScript, or TrustedScriptURL object instead of a raw string.
Enable Trusted Types in your CSP header with: require-trusted-types-for 'script'. Then configure your application to use Trusted Types for:
Dynamic HTML injection (if you must use innerHTML, wrap it in a Trusted Types policy).Script URL creation (for dynamically created script elements).Redirect URLs (for window.location assignments).This is a defense-in-depth measure. Even if an XSS vulnerability exists, Trusted Types prevents the most common exploitation patterns.
State Management Security
Client-side state is often overlooked as a security surface. Sensitive data in Redux, Zustand, or React state can be:
Inspected via browser DevTools.Persisted to localStorage (which is accessible to any script on the page).Serialized in React DevTools component tree.Leaked via error boundaries that render state to the DOM.Rules for sensitive data in frontend state:
Never store authentication tokens in localStorage or sessionStorage. Use httpOnly cookies with Secure and SameSite flags.Keep sensitive values in component-local state, not global stores.Never log sensitive data to the console in production builds.Strip sensitive fields from error boundary output.Backend Security: The Foundation
Input Validation Is Not Optional
The most common backend vulnerability is trusting user input. Every piece of data that enters your application from an external source must be validated, sanitized, and typed before use.
Implement validation at three boundaries:
API boundary: Validate request shape, types, and ranges before the request reaches your business logic. Use schema validation libraries (Zod, Yup, io-ts) to define strict contracts.Business logic boundary: Validate domain invariants. Does this user have permission to perform this action? Is this value within acceptable bounds for this operation?Data boundary: Validate before writing to the database. Never construct SQL queries or ORM calls with unsanitized input.Authentication Patterns That Hold Up
In 2026, the authentication landscape has settled on a few reliable patterns:
Session-based auth with httpOnly cookies: The most secure option for server-rendered and hybrid applications. Tokens are never exposed to JavaScript, immune to XSS token theft, and automatically sent with requests.Short-lived access tokens with refresh rotation: For SPAs and mobile apps. Access tokens expire in 15 minutes, refresh tokens rotate on every use, and refresh token reuse detection immediately invalidates the session.Passkeys (WebAuthn): Phishing-resistant authentication that is gaining mainstream adoption. Passwords are replaced by cryptographic key pairs stored on the user's device.Avoid storing JWTs in localStorage. This is a known anti-pattern that remains prevalent despite years of guidance. localStorage is accessible to any script on the page, including XSS payloads.
Authorization: The Hardest Problem
Authorization bugs consistently rank as the top vulnerability category. The core challenge is that authorization logic is distributed across the application and easy to get wrong.
Practical patterns:
Row-Level Security (RLS): Push authorization to the database layer. PostgreSQL RLS policies ensure that even if your application code has an authorization bug, the database enforces access control.Middleware-based RBAC: Apply role checks in middleware before the request reaches your handler. This centralizes the logic and prevents accidental bypass.Policy-as-code: Define authorization policies in a dedicated module, not scattered across handlers. Test them independently with a comprehensive test matrix.Deny by default: Every endpoint should deny access unless an explicit policy grants it. This prevents the common mistake of forgetting to add authorization to a new endpoint.Infrastructure Security
Supply Chain Attacks
The xz-utils backdoor in 2024 demonstrated that supply chain attacks can target even widely-used open source libraries. In 2026, supply chain security is a first-class concern.
Defensive measures:
Lock your dependencies: Use lockfiles and verify their integrity. Never run npm install without a lockfile in production.Audit dependencies: Run npm audit or equivalent regularly. Prioritize critical and high-severity advisories.Pin dependency versions: Use exact versions for security-critical packages. A semver range that accepts a compromised version gives you no protection.Verify package integrity: Use npm's built-in checksum verification. Consider running a private registry mirror that you control.Monitor for typosquatting: Automated tools that scan for package name similarities can catch typosquatting attacks before they reach your dependencies.Security Headers
A complete set of security headers for a production application:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload – Force HTTPS for two years.X-Content-Type-Options: nosniff – Prevent MIME type sniffing.X-Frame-Options: DENY – Prevent framing (clickjacking protection).Referrer-Policy: strict-origin-when-cross-origin – Limit referrer information leakage.Permissions-Policy: camera=(), microphone=(), geolocation=() – Disable unnecessary browser features.Cross-Origin-Opener-Policy: same-origin – Isolate your origin from cross-origin references.Cross-Origin-Resource-Policy: same-origin – Prevent cross-origin resource loading.Database Security
Encrypt at rest: Use database-level encryption for all data at rest.Encrypt in transit: Enforce TLS for all database connections, even internal ones.Least privilege: Application database users should only have the permissions they need. Read-only replicas for read operations, write access only for write operations.Connection pooling: Limit maximum connections to prevent resource exhaustion attacks.Query parameterization: Never construct queries with string concatenation. Use parameterized queries exclusively.Monitoring and Incident Response
Security monitoring is not a nice-to-have. It is a requirement for any application handling sensitive data.
Essential monitoring:
Authentication events: Log every login attempt, failure, and logout. Alert on brute-force patterns.Authorization failures: Log every denied access attempt. A spike in authorization failures may indicate an attack.Data access patterns: Monitor for unusual data access volumes. A user downloading 10,000 records in a minute is likely not legitimate behavior.Error rates: Sudden spikes in 4xx or 5xx errors may indicate an active attack.Dependency changes: Monitor for unexpected changes in your dependency tree.Incident response is not about prevention; it is about containment. Have a documented, practiced runbook. Know who to contact, how to isolate the affected system, and how to communicate with users.
Conclusion
Full-stack security in 2026 is not about any single technique. It is about layered defenses: CSP and Trusted Types on the frontend, input validation and authorization on the backend, supply chain hygiene and encryption in infrastructure, and monitoring across all layers.
The OWASP Top 10:2025 gives you a checklist. The patterns in this guide give you implementation guidance. But the real protection comes from building security into your development process, not bolting it on after deployment.
At RedFortLabs, security is not a separate service we offer; it is how we build every application. The patterns in this guide are the same patterns we apply to our own systems and our clients' systems. Security is not a feature; it is a property of well-engineered software.