Security can be an intimidating topic for web developers. The vocabulary is rich and full of acronyms. Trends evolve quickly as hackers and analysts play a perpetual cat-and-mouse game. Vulnerabilities stem from little details we cannot afford to spend too much time on during our day-to-day operations.
JavaScript developers already have a lot to take with the emergence of a new wave of innovative architectures, such as React Server Components, Next.js App Router, or Astro islands.
So, let’s have a focused approach. What we need is to be able to detect and palliate the most common security issues. A top ten of the most common vulnerabilities would be ideal.
Meet The OWASP Top 10
Guess what: there happens to be such a top ten of the most common vulnerabilities, curated by experts in the field!
It is provided by the OWASP Foundation, and it’s an extremely valuable resource for getting started with security.
OWASP stands for “Open Worldwide Application Security Project.” It’s a nonprofit foundation whose goal is to make software more secure globally. It supports many open-source projects and produces high-quality education resources, including the OWASP top 10 vulnerabilities list.
We will dive through each item of the OWASP top 10 to understand how to recognize these vulnerabilities in a full-stack application.
Note: I will use Next.js as an example, but this knowledge applies to any similar full-stack architecture, even outside of the JavaScript ecosystem.
Let’s start our countdown towards a safer web!
Number 10: Server-Side Request Forgery (SSRF)
You may have heard about Server-Side Rendering, aka SSR. Well, you can consider SSRF to be its evil twin acronym.
Server-Side Request Forgery can be summed up as letting an attacker fire requests using your backend server. Besides hosting costs that may rise up, the main problem is that the attacker will benefit from your server’s level of accreditation. In a complex architecture, this means being able to target your internal private services using your own corrupted server.

Here is an example. Our app lets a user input a URL and summarizes the content of the target page server-side using an AI SDK. A mischievous user passes localhost:3000
as the URL instead of a website they’d like to summarize. Your server will fire a request against itself or any other service running on port 3000 in your backend infrastructure. This is a severe SSRF vulnerability!
You’ll want to be careful when firing requests based on user inputs, especially server-side.
Number 9: Security Logging And Monitoring Failures
I wish we could establish a telepathic connection with our beloved Node.js server running in the backend. Instead, the best thing we have to see what happens in the cloud is a dreadful stream of unstructured pieces of text we name “logs.”
Yet we will have to deal with that, not only for debugging or performance optimization but also because logs are often the only information you’ll get to discover and remediate a security issue.
As a starter, you might want to focus on logging the most important transactions of your application exactly like you would prioritize writing end-to-end tests. In most applications, this means login, signup, payouts, mail sending, and so on. In a bigger company, a more complete telemetry solution is a must-have, such as Open Telemetry, Sentry, or Datadog.
If you are using React Server Components, you may need to set up a proper logging strategy anyway since it’s not possible to debug them directly from the browser as we used to do for Client components.
Number 8: Software And Data Integrity Failures
The OWASP top 10 vulnerabilities tend to have various levels of granularity, and this one is really a big family. I’d like to focus on supply chain attacks, as they have gained a lot of popularity over the years.
You may have heard about the Log4J vulnerability. It was very publicized, very critical, and very exploited by hackers. It’s a massive supply chain attack.
In the JavaScript ecosystem, you most probably install your dependencies using NPM. Before picking dependencies, you might want to craft yourself a small list of health indicators.
- Is the library maintained and tested with proper code?
- Does it play a critical role in my application?
- Who is the main contributor?
- Did I spell it right when installing?
For more serious business, you might want to consider setting up a Supply Chain Analysis (SCA) solution; GitHub’s Dependabot is a free one, and Snyk and Datadog are other famous actors.
Number 7: Identification And Authentication Failures
Here is a stereotypical vulnerability belonging to this category: your admin password is leaked. A hacker finds it. Boom, game over.
Password management procedures are beyond the scope of this article, but in the context of full-stack web development, let’s dive deep into how we can prevent brute force attacks using Next.js edge middlewares.
Middlewares are tiny proxies written in JavaScript. They process requests in a way that is supposed to be very, very fast, faster than a normal Node.js endpoint, for example. They are a good fit for handling low-level processing, like blocking malicious IPs or redirecting users towards the correct translation of a page.
One interesting use case is rate limiting. You can quickly improve the security of your applications by limiting people’s ability to spam your POST endpoints, especially login and signup.
You may go even further by setting up a Web Applications Firewall (WAF). A WAF lets developers implement elaborate security rules. This is not something you would set up directly in your application but rather at the host level. For instance, Vercel has released its own WAF in 2024.
Number 6: Vulnerable And Outdated Components
We have discussed supply chain attacks earlier. Outdated components are a variation of this vulnerability, where you actually are the person to blame. Sorry about that.
Security vulnerabilities are often discovered ahead of time by diligent security analysts before a mean attacker can even start thinking about exploiting them. Thanks, analysts friends! When this happens, they fill out a Common Vulnerabilities and Exposure and store that in a public database.
The remedy is the same as for supply chain attacks: set up an SCA solution like Dependabot that will regularly check for the use of vulnerable packages in your application.

Halfway break
I just want to mention at this point how much progress we have made since the beginning of this article. To sum it up:
- We know how to recognize an SSRF. This is a nasty vulnerability, and it is easy to accidentally introduce while crafting a super cool feature.
- We have identified monitoring and dependency analysis solutions as important pieces of “support” software for securing applications.
- We have figured out a good use case for Next.js edge middlewares: rate limiting our authentication endpoints to prevent brute force attacks.
It’s a good time to go grab a tea or coffee. But after that, come back with us because we are going to discover the five most common vulnerabilities affecting web applications!
Number 5: Security Misconfiguration
There are so many configurations that we can mismanage. But let’s focus on the most insightful ones for a web developer learning about security: HTTP headers.
You can use HTTP response headers to pass on a lot of information to the user’s browser about what’s possible or not on your website.
For example, by narrowing down the “Permissions-Policy” headers, you can claim that your website will never require access to the user’s camera. This is an extremely powerful protection mechanism in case of a script injection attack (XSS). Even if the hacker manages to run a malicious script in the victim’s browser, the latter will not allow the script to access the camera.
I invite you to observe the security configuration of any template or boilerplate that you use to craft your own websites. Do you understand them properly? Can you improve them? Answering these questions will inevitably lead you to vastly increase the safety of your websites!
Number 4: Insecure Design
I find this one funny, although a bit insulting for us developers.
Design is actually not just about code but about the way we use our programming tools to produce software artifacts.

In the context of full-stack JavaScript frameworks, I would recommend learning how to use them idiomatically, the same way you’d want to learn a foreign language. It’s not just about translating what you already know word-by-word. You need to get a grasp of how a native speaker would phrase their thoughts.
Learning idiomatic Next.js is really, really hard. Trust me, I teach this framework to web developers. Next is all about client and server logic hybridization, and some patterns may not even transfer to competing frameworks with a different architecture like Astro.js or Remix.
Hopefully, the Next.js core team has produced many free learning resources, including articles and documentation specifically focusing on security.
I recommend reading Sebastian Markbåge’s famous article “How to Think About Security in Next.js” as a starting point. If you use Next.js in a professional setting, consider organizing proper training sessions before you start working on high-stakes projects.
Number 3: Injection
Injections are the epitome of vulnerabilities, the quintessence of breaches, and the paragon of security issues. SQL injections are typically very famous, but JavaScript injections are also quite common. Despite being well-known vulnerabilities, injections are still in the top 3 in the OWASP ranking!
React doesn’t want you to include user input that could contain a malicious script.
The screenshot below is a demonstration of an injection using images. It could target a message board, for instance. The attacker misused the image posting system. They passed a URL that points towards an API GET endpoint instead of an actual image. Whenever your website’s users see this post in their browser, an authenticated request is fired against your backend, triggering a payment!
As a bonus, having a GET endpoint that triggers side-effects such as payment also constitutes a risk of Cross-Site Request Forgery (CSRF, which happens to be SSRF client-side cousin).

Even experienced developers can be caught off-guard. Are you aware that dynamic route parameters are user inputs? For instance, [language]/page.jsx
in a Next.js or Astro app. I often see clumsy attack attempts when logging them, like “language” being replaced by a path traversal like ../../../../passwords.txt
.
Zod is a very popular library for running server-side data validation of user inputs. You can add a transform step to sanitize inputs included in database queries, or that could land in places where they end up being executed as code.
Number 2: Cryptographic Failures
A typical discussion between two developers that are in deep, deep trouble:
— We have leaked our database and encryption key. What algorithm was used to encrypt the password again? AES-128 or SHA-512?
— I don’t know, aren’t they the same thing? They transform passwords into gibberish, right?
— Alright. We are in deep, deep trouble.
This vulnerability mostly concerns backend developers who have to deal with sensitive personal identifiers (PII) or passwords.
To be honest, I don’t know much about these algorithms; I studied computer science way too long ago.
The only thing I remember is that you need non-reversible algorithms to encrypt passwords, aka hashing algorithms. The point is that if the encrypted passwords are leaked, and the encryption key is also leaked, it will still be super hard to hack an account (you can’t just reverse the encryption).
In the State of JavaScript survey, we use passwordless authentication with an email magic link and one-way hash emails, so even as admins, we cannot guess a user’s email in our database.

And number 1 is…
Such suspense! We are about to discover that the top 1 vulnerability in the world of web development is…
Broken Access Control! Tada.
Yeah, the name is not super insightful, so let me rephrase it. It’s about people being able to access other people’s accounts or people being able to access resources they are not allowed to. That’s more impressive when put this way.
A while ago, I wrote an article about the fact that checking authorization within a layout may leave page content unprotected in Next.js. It’s not a flaw in the framework’s design but a consequence of how React Server Components have a different model than their client counterparts, which then affects how the layout works in Next.
Here is a demo of how you can implement a paywall in Next.js that doesn’t protect anything.
// app/layout.jsx
// Using cookie-based authentication as usual
async function checkPaid() {
const token = cookies.get("auth_token");
return await db.hasPayments(token);
}
// Running the payment check in a layout to apply it to all pages
// Sadly, this is not how Next.js works!
export default async function Layout() {
// ❌ this won't work as expected!!
const hasPaid = await checkPaid();
if (!hasPaid) redirect("/subscribe");
// then render the underlying page
return <div>{children}</div>;
}
// ❌ this can be accessed directly
// by adding “RSC=1” to the request that fetches it!
export default function Page() {
return <div>PAID CONTENT</div>
}
What We Have Learned From The Top 5 Vulnerabilities
Most common vulnerabilities are tightly related to application design issues:
- Copy-pasting configuration without really understanding it.
- Having an improper understanding of the framework we use in inner working. Next.js is a complex beast and doesn’t make our life easier on this point!
- Picking an algorithm that is not suited for a given task.
These vulnerabilities are tough ones because they confront us to our own limits as web developers. Nobody is perfect, and the most experienced developers will inevitably write vulnerable code at some point in their lives without even noticing.
How to prevent that? By not staying alone! When in doubt, ask around fellow developers; there are great chances that someone has faced the same issues and can lead you to the right solutions.
Where To Head Now?
First, I must insist that you have already done a great job of improving the security of your applications by reading this article. Congratulations!
Most hackers rely on a volume strategy and are not particularly skilled, so they are really in pain when confronted with educated developers who can spot and fix the most common vulnerabilities.

From there, I can suggest a few directions to get even better at securing your web applications:
- Try to apply the OWASP top 10 to an application you know well, either a personal project, your company’s codebase, or an open-source solution.
- Give a shot at some third-party security tools. They tend to overflow developers with too much information but keep in mind that most actors in the field of security are aware of this issue and work actively to provide more focused vulnerability alerts.
- I’ve added my favorite security-related resources at the end of the article, so you’ll have plenty to read!
Thanks for reading, and stay secure!
Resources For Further Learning
This article is inspired by my talk at React Advanced London 2024, “Securing Server-Rendered Applications: Next.js case,” which is available to watch as a replay online.

(yk)