3 “Golden” Rules to Follow to Become a Security-first React.js Developer

manthony
5 min readDec 12, 2020
You can never be sure who your user really is!

As a web developer, you are the first line of defense against security vulnerabilities within your application. Sure, there are professionals who specialize in security- and the tools they have mastered are very powerful. These tools include both Static and Dynamic Application Security Testing (software that can analyze and detect security threats automatically), Software Composition Analysis Software (analyze the many vulnerabilities that are imported as modules from open source code), and Penetration Testing (manual tests to validate controls and identify vulnerabilities and the risk associated with them). All of these tools are mere safeguards and many of them reactionary- the most powerful tool to create (or defend against) vulnerabilities is you. Below are three rules to follow to make sure you are doing your part in prevention.

As a web developer, you are the first line of defense against security vulnerabilities within your application.

First, let us go over some basic fundamentals of the web- of which JS is the king. JS has full read/write access of the DOM, or Document Object Model, which is the standard tree structure of nodes that represent a webpage. The DOM gets its data over HTTP, or Hypertext Transfer Protocol, which can be thought of as a tunnel that can transmit documents’ data between a client and a server. Browsers like Chrome and Firefox act as these clients that to talk to web servers and are like sandboxes for JS and HTTP, allowing for a safe space for them to play together. We trust these browsers to secure this sandbox- and in most cases, they do this very well. But it is still possible for some malicious individuals to slither into your sandbox and cause some mayhem.

1. Never trust user input

Users are a huge threat to your application. Even if the user is authenticated, you can’t be sure that everything they are doing is harmless or if they themselves meant to become authenticated in the first place (especially in the case of cookies).

One way to prevent malicious behavior is to relentlessly validate and clean user input on the client. Some great libraries exist to help us with this- most notably Formik which offers a great interface to validate input at the form or field level. Using Yup in conjunction with Formik will allow you to create robust validation schemas that can filter the type, error message, length, and format of input so you can be sure what you are sending to your server is not harmful.

Below is an example of using Formik and Yup to create a user signup form.

import React from 'react';
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
const SignupSchema = Yup.object().shape({ firstName: Yup.string()
.min(2, 'Too Short!')
.max(50, 'Too Long!')
.required('Required'),
lastName: Yup.string()
.min(2, 'Too Short!')
.max(50, 'Too Long!')
.required('Required'),
email: Yup.string()
.email('Invalid email')
.required('Required'),
});
export const ValidationSchemaExample = () => (
<div>
<h1>Signup</h1>
<Formik
initialValues={{
firstName: '',
lastName: '',
email: '',
}}
validationSchema={SignupSchema}
onSubmit={values => {
// same shape as initial values
console.log(values);
}}
>
{({ errors, touched }) => (
<Form>
<Field name="firstName" />
{errors.firstName && touched.firstName ? (
<div>{errors.firstName}</div>
) : null}
<Field name="lastName" />
{errors.lastName && touched.lastName ? (
<div>{errors.lastName}</div>
) : null}
<Field name="email" type="email" />
{errors.email && touched.email
?
<div>{errors.email}</div>
: null}
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</div>
);

2. Don’t allow unvetted HTML to be injected back into the DOM

Letting a user submit HTML to be rendered in the DOM of your application can result in an XSS or cross-origin scripting attack. When you are creating a component in React, it will not treat HTML strings the same way as the natrive innerHTML method does. Example below:

function App() {
const profileMarkup = '<div><h1>Welcome to my profile<h1></div>'
return (
<MyProfile htmlMarkup={profileMarkup}/>
);
};
function MyProfile({ htmlMarkup }) {
return (
{htmlMarkup}
);
};

In this instance, React will take your request quite literally and render the component like this:

<div><h1>Welcome to my profile<h1></div>

The only way to bypass this feature is to use a special attribute in react, dangerouslySetInnerHTML. On top of that very ominous name, the param it takes is an object with a single property, __html. Example below:

function App() {
const profileMarkup = {__html: '<h1>Welcome to my profile<h1>'}
return (
<MyProfile htmlMarkup={profileMarkup}/>
);
};
function MyProfile({ htmlMarkup }) {
return (
<div dangerouslySetInnerHTML={htmlMarkup}/>
);
};
The result of using dangerouslySetInnerHTML attr

There are also several libraries I would recommend, such as DOMPurify that can sanitize XSS potentials throughout the HTML you are setting.

3. Understand Same-Origin-Policy and Cross-Origin-Reference-Sharing

CORS (Cross Origin Reference Sharing) and SOP (Same Origin Policy) are features, not bugs- and they can give you a useful tool to withold or grant access to your served data thus preventing malicious behavior to fool a browser into revealing a user’s session data.

What CORS and SOP have in common is what they are interested in- the host, protocol, and port, or collectively “origin"being identical (or whitelisted). SOP plays this one hard and fast- there are no compromises in this comparison process.

CORS is a HTTP mechanism that uses headers within a request to loosen some of these policies enforced by SOP. In development, there may be workarounds to like disabling the same-origin policy in your browser (here is a chrome extension that will do this at the click of a button), or using a middleware to configure your backend to be very lenient (two common backend frameworks configs below) to allow access

# DJANGO CORS CONFIG EXAMPLE

...add these to 'MIDDLEWARE' section in project's settings.py:
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...and then paste this at bottom of project's settings.py
CORS_ORIGIN_ALLOW_ALL = True
# EXPRESS CORS CONFIG EXAMPLE

var express = require('express')
var cors = require('cors')
var app = express()

app.use(cors())

app.get('/products/:id', function (req, res, next) {
res.json({msg: 'This is CORS-enabled for all origins!'})
})

app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})

However, good developers enforce tight CORS policies in production to protect the users. Even if you are dealing with data that is not sensitive, if your intention is not to serve that data to virtually everyone then CORS policies should be thought through and understood.

A dog-loving transitioning Marine vet from New Haven, CT that thoroughly enjoys learning programming fundamentals and sharing them to solidify his own learning and thoughtfully engage with others.

https://grandorimichael.medium.com/

--

--