5 best practices for React with TypeScript security
Tags: applicationsecurity, ecosystems
Published: 2022-12-20 13:42:18

---
title: 5 best practices for React with TypeScript security
published: true
date: 2022-12-07 22:03:35 UTC
tags: ApplicationSecurity,Ecosystems
canonical_url: https://snyk.io/blog/best-practices-react-typescript-security/
---

As a library focused on building user interfaces rather than a full-fledged framework, React enables developers to choose their preferred libraries for various aspects of an application, such as routing, history, and authentication. Comparatively, Microsoft created TypeScript as an extension of JavaScript to introduce optional static typing to an otherwise loosely typed language.

Using TypeScript with React provides several advantages in application-building, including the option of simpler React components and better JavaScript XML (JSX) support for static type validation. As we can use JavaScript components in a TypeScript project, development teams with JavaScript experience can leverage this knowledge to benefit from strong-typing programming.

Many [boilerplates](https://pt-br.reactjs.org/community/starter-kits.html) are available for starter React projects, including [Create React App](https://create-react-app.dev/), [Create Next App](https://nextjs.org/docs/api-reference/create-next-app), [Vite](https://vitejs.dev/), [React Boilerplate](https://www.reactboilerplate.com/), and [React Starter Kit](https://firebase.reactstarter.com/).

[Create React App](https://create-react-app.dev/) is a standalone tool that can run with either npm or Yarn. Once installed, we can generate and run a new project with just a few commands. 

First, open your terminal and run the following command to install the Create React App tool:

```
npm install -g create-react-app
```

Then, create a project using the TypeScript template with node package execute `(npx)`:

```
npx create-react-app [webapp-name] --template typescript
```

Alternatively, you can create the project using the Yarn package manager:

```
yarn create react-app [webapp-name] --template typescript
```

potential security risks, and how to mitigate them.

## Enable strict mode

Strict mode automatically turns on the TypeScript compiler parameters related to data type rules. When TypeScript strict mode is enabled, it validates the code using the strict type rules, forcing developers to write code respecting the limitations of the data types assigned to variables, constants, parameters, and function return values. Strict mode is important because it enables developers to catch and fix type mismatch bugs early in the development phase.

Suppose an application has the following function:

```
export async function deleteComment(slug, commentId): Promise<void> {
  await axios.delete(`articles/${slug}/comments/${commentId}`);
}
```

We invoke the `deleteComment` function by passing the `slug` argument as a string and `commentId` as a number:

```
deleteComment('whats-new-in-react', 1257);
```

However, this doesn’t prevent us from invoking the function with two strings:

```
deleteComment('foo', 'bar');
```

The code above may not be allowed by business rules, but since we haven’t provided types, both parameters are assumed to be of the default `any` type. Consequently, TypeScript can’t help us identify the problem unless we enable strict mode.

Newly created React applications come with the `strict` value set to `true`. However, this value may differ for existing projects.

To ensure that our TypeScript project is running in strict mode, open the `tsconfig.json` file and check that the value of the strict configuration is `true`:

```
{
  ...
  "strict": true,
  ...
}
```

Return to the `deleteComment` function. One small change immediately produces TypeScript compilation errors:

![](https://snyk.io/wp-content/uploads/blog-react-typescript-conduit-ts-parameter.jpg)

Add the string and number types to the `deleteComment` parameters:

```
export async function deleteComment(slug: string, commentId: number): Promise<void> {
  await axios.delete(`articles/${slug}/comments/${commentId}`);
}
```

This produces compilation errors, preventing developers from creating client code that inadvertently calls the `deleteComment` function with invalid types, like in the example below:

```
deleteComment(null, true);
```

![](https://snyk.io/wp-content/uploads/blog-react-typescript-conduit-ts-argument.jpg)

The strict option automatically enables other recommended compiler options related to stricter [type-checking](https://www.typescriptlang.org/tsconfig#Type_Checking_6248).

## Don’t use return type `any` in callbacks whose value will be ignored

If you declare a callback’s return type as `any`, then inadvertently use its return value when the function doesn’t return a value, you can make a mistake that goes undetected.

Consider a function named `onListFieldKeyUp` that takes a callback named `onEnter` as a parameter:

```
export function onListFieldKeyUp(onEnter: () => any): (ev: React.KeyboardEvent) => void {
  return (ev) => {
    if (ev.key === 'Enter') {
      ev.preventDefault();	  
      var enterResult = onEnter();
      //do something with enterResult
      }
    }
  };
}
```

Note how the `enterResult` variable above stores the result of the `onEnter` callback function for further use. Although the callback doesn’t return a value, we declared the `enterResult` variable with type `any`, so TypeScript is unable to alert you to a problem. 

What’s the solution to this? 

First, if you know the `onEnter` function doesn’t return a value, replace the `any` type in the callback parameter with `void`. Now, TypeScript displays the error “An expression of type ‘void’ cannot be tested for truthiness.

![](https://snyk.io/wp-content/uploads/blog-react-typescript-form-group.jpg)

Finally, remove the code that stores the return value of `onEnter` in the `enterResult` variable:

```
export function onListFieldKeyUp(onEnter: () => void): (ev: React.KeyboardEvent) => void {
  return (ev) => {
    if (ev.key === 'Enter') {
      ev.preventDefault();
      onEnter();
    }
  };
}
```

## Server-side rendering attacks in React

A web application can render HTML on the client or the server. Modern JavaScript frameworks and libraries, like React, adopt server-side rendering. This approach provides performance benefits, including accelerated page loading — allowing the back end to quickly pre-render the entire page and pass the static HTML, CSS, and JavaScript content to the front end. As a result, users can navigate and see the web page instantly. This approach also helps with search engine optimization (SEO) since fast-loading pages reach higher scores in search algorithms.

Cross-site scripting (XSS) is an attack modality where attackers inject malicious client-side scripts into a web page. React was designed to be safe from XSS. However, improper programming and server-side rendering in React can lead to a XSS vulnerability that malicious users will exploit.

For example, never concatenate unsanitized data with the output of the `renderToStaticMarkup` function before sending the string to the client:

```
app.get("/", function (req, res) {
  return res.send(
    ReactDOMServer.renderToStaticMarkup(
      React.createElement("h1", null, "Hello World!")
    ) + someUnsanitizedData
  );
});
```

The code above is unsafe because a hacker may have compromised the `someUnsanitizedData` variable to include malicious JavaScript code, like below:

```
someUnsanitizedData = "</scrïpt><scrïpt>alert('You are compromised!')</scrïpt>
```

To prevent XSS attacks, use an HTML sanitizer such as [DomPurify](https://www.npmjs.com/package/dompurify).

## Use opaque types 

An opaque data type enforces information-hiding. Its data structure is not defined in the interface, which hides and encapsulates the implementation of a concrete data type. While external modules can use the opaque type without accessing its internals, internal functions with access to the missing information can manipulate the type. Opaque types enable you to change and evolve internal details without changing the code that uses them. Therefore, using them remains a development best practice.

While TypeScript does not provide opaque types out of the box, we can implement them easily. Let’s try solving a real-world use case.

Imagine an e-commerce application using a function in TypeScript to add a product to the customer cart: 

```
function addToCart(customerCode: string, productCode: string) {
  console.log(`Product ${productCode} has been added to the cart of the customer ${customerCode}`);
}
```

These typed parameters ensure the programmer won’t provide numbers or other types in place of strings for the `customerCode` and `productCode` parameters:

```
addToCart('ABC-001984', 'SPC-004487');
```

Though there isn’t anything wrong with the code, the type fails to precisely express the values. You can’t tell which parameter is the customer or product code just by looking at the code line, and TypeScript won’t give a warning if both values are swapped.

Now look at how the `addToCart` was refactored in the example below to use opaque types:

```
export type CustomerCode = string & { _: 'CustomerCode' };
export type ProductCode = string & { _: 'ProductCode' };

const makeCustomerCode =
  (customerCode: string): CustomerCode => {
    if (/^\w{3}-\d{6}$/.test(customerCode)) { //regex validation
      return customerCode as CustomerCode;
    } else {
      throw new Error('Not a customer code!');
    }
  };

const makeProductCode =
  (productCode: string): ProductCode => {
    if (/^\w{3}-\d{6}$/.test(productCode)) { //regex validation
      return productCode as ProductCode;
    } else {
      throw new Error('Not a product code!');
    }
  };

function addToCart(customerCode: CustomerCode, productCode: ProductCode) {
  console.log(`Product ${productCode} has been added to the cart of the customer ${customerCode}`);
}

let customerCode: CustomerCode = makeCustomerCode('ABC-001984');
let productCode: ProductCode = makeProductCode('SPC-004487');

addToCart(customerCode, productCode);
```

Thanks to our new opaque types and their regex validation, the code produces a compiler error if you provide incorrect codes:

```
let customerCode: CustomerCode = makeCustomerCode('000-001984'); //Error: Not a customer code!
let productCode: ProductCode = makeProductCode('9871'); //Error: Not a product code!
```

## When to use `dangerouslySetInnerHTML` and observe proper sanitization practices

React uses a [Virtual DOM](https://snyk.io/advisor/npm-package/virtual-dom#readme) as a lightweight strategy to update the page’s HTML efficiently, preventing users from having to deal with native browser APIs directly to manipulate HTML elements.

However, at times you may have to override this mechanism and set raw HTML code in your React applications. To directly manipulate this HTML code, React uses a special component property called `dangerouslySetInnerHTML`:

```
return (
<p dangerouslySetInnerHTML={{__html: data}}></p>);
```

As the name suggests, the `dangerouslySetInnerHTML` property makes an application vulnerable if not used properly. Hackers can exploit applications relying on `dangerouslySetInnerHTML` to perform [Cross-Site Scripting](https://learn.snyk.io/lessons/xss/javascript/) (XSS) attacks and inject malicious scripts disguised as trusted user input into your website.

To prevent this risk, always sanitize the HTML content before inserting it to eliminate “impurities” or malicious code. You can use a library like `DOMPurify` and apply the `sanitize` function: 

```
import DOMPurify from 'dompurify';

return (
<p dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(data)}}></p>);
```

It’s crucial to note that, like any other library, `DOMPurify` is prone to the occasional security vulnerability. Fortunately, the [Snyk vulnerability database](https://security.snyk.io/package/npm/dompurify) quickly incorporated this vulnerability data and provided a solution. So, regardless of a library’s stable history, we should still practice our due diligence by using a tool like Snyk to periodically scan for any known vulnerabilities. 

And that’s just one of the security concerns developers should consider. In this video, Liran Tal discusses how React developers can make mistakes, leading to other vulnerabilities that hackers can exploit.

<iframe loading="lazy" title="Liran Tal - You thought your React application is secure? Think again | ReactNext 2021" width="500" height="281" src="https://www.youtube.com/embed/eOKJnjuDaqg?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## Conclusion

In this article, we discussed some best practices for developing React applications with TypeScript, outlined potential security risks, and prescribed solutions.

- Strict mode allows you to enforce type constraints and catch type mismatch mistakes early in development. Using `void` in callbacks prevents developers from using a return value from callbacks that don’t return a value.
- Command injection attacks are a serious security threat. Avoid building dynamic code with suspicious user input and use the `execFile` function instead of `exec`.
- HTML injections are another serious security threat. When using `dangerouslySetInnerHTML`, always sanitize the inserted markup to prevent attackers from successfully tampering with user input and injecting malicious code.
- Finally, opaque types provide meaningful domains and help you more easily validate types and avoid duplicates.

It’s easy to use TypeScript and React to build fast, secure applications, especially using frameworks like Create React App and previous knowledge of JavaScript.