Secure Server-Side Validation with TypeScript

Introduction

In our previous courses, we took a deep dive into client-side validation when working on the user registration feature. However, it’s crucial to note that client-side validation can be bypassed. Attackers can deliberately circumvent client-side restrictions, emphasizing the importance of robust server-side validation. Let’s now shift our focus to the snippets part of our application and demonstrate secure server-side validation using TypeScript.

Understanding Server-Side Validation

Server-side validation is your final gatekeeper to ensure that data is clean and meets the expected requirements. Even if malicious users bypass client-side checks, robust server-side validation will stop unsafe or malformed data from compromising your application. TypeScript’s strict typing, combined with libraries like Zod, offers a structured way to define and enforce these rules. While TypeScript enforces type safety at compile time, ensuring that variables and functions receive the expected types before the code is run, it is erased at runtime. This means incoming user input is still just a raw JavaScript object, allowing attackers to send unexpected values, such as null, objects instead of strings, or excessively large inputs. Therefore, using runtime validation libraries like Zod is essential to dynamically enforce data constraints.

Vulnerable Code Example

Below is a snippet of our “save snippet” endpoint. Currently, there is no server-side validation in place to verify the data contained in the request body:

TypeScript
router.post('/', async (req, res) => {
  const authHeader = req.headers.authorization;
  if (!authHeader) {
    return res.status(401).json({ error: "Missing authorization header" });
  }
  const token = authHeader.split(' ')[1];
  let decoded: any;
  try {
    decoded = jwt.verify(token, JWT_SECRET_KEY);
  } catch (error) {
    return res.status(401).json({ error: "Invalid token" });
  }
  const userId = decoded.userId;
  try {
    const { title, content, language } = req.body;
    const snippet = await Snippet.create({
      id: uuidv4(),
      title,
      content,
      language,
      userId,
    });
    res.json(snippet);
  } catch (error) {
    console.error("Error saving snippet:", error);
    res.status(500).json({ error: "Failed to save snippet" });
  }
});

Since the snippet is directly created from user input, attackers could insert malicious data (e.g., harmful scripts) into these fields.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal