January 18, 2018
|
Under node.js
(12)
, javascript
(4)
, coding
(15)

Building Secure JavaScript Applications

A few weeks back, I've attended SFNode, where Randall Degges gave a presentation on JWTs, mostly on why you avoid using them. The talk was amazing, and also reminded me of an article I wanted to write for a long time now - how one can build secure JavaScript applications. Here we go!

In this article, I will go through the most frequently asked question about how one can make a JavaScript application more secure.

How can I protect against XSS?

XSS, or Cross-Site Scripting, is a technique that enables attackers to run externally injected JavaScript in the context of the attacked page. Once the attackers manage to do so, it can access the full range of Web APIs.

The simplest XSS attack may look the following:

  • The attacker finds input fields on the webpage
  • The attacker starts inputting simple HTML and JavaScript to see if the page vulnerable, like <script>alert('ops!')</script>
  • Once the attacker finds such an input that is vulnerable, the attacker crafts a link that will inject a give snipper into the page, and sends it to the attacked person
  • After the link is opened, it is up to injected script and the attacker on what's going to happen next

The reason why XSS attacks rank in the top 10 OWASP security risks, is that using this exploit, attackers can easily get access to secrets stored in LocalStorage, SessionStorage or even cookies. This is the main reason why OWASP recommends never to store sensitive information in these storages. Once the attackers manage to read them, they can potentially impersonate the attacked user accounts.

Defending against XSS attacks are not trivial - you have to make sure you never inject unknown user input into the page. You must use the escape syntax for the part of the HTML document you're putting untrusted data into:

  • HTML escape before putting untrusted data into HTML
  • JavaScript escape before putting untrusted data into JavaScript data values
    • However, some JavaScript functions can never take untrusted input:
      • setInterval,
      • new Function,
      • setTimeout,
      • basically any function, that evaluates code.
  • CSS escape and validate before putting data into HTML style properties
    • This is more and more pressing, as a lot of React components chose this approach

To learn more about XSS prevention, read the XSS Prevention Cheat Sheet: https://www.owasp.org/index.php/XSS(CrossSite_Scripting)_Prevention_Cheat_Sheet.

How should I store passwords?

Never ever in plain text. Also, never ever without salt. Without salt, your passwords can be reversed using Rainbow tables.

To salt your passwords, you can use Bcrypt or Scrypt. Both of them have implementation for Node.js applications: bcrypt and scrypt. You can pick either, your users' password will be protected.

How can I protect against CSRF?

CSRF or Cross-Site Request Forgery is an attack vector which exploits the way HTTP requests are sent from the browser: if a user has cookies for the site foo.com, no matter who starts a given request, cookies set by foo.com will always be sent with the request.

It becomes an elevated risk, once your users are logged in. If you don't defend your application against CSRF attacks, you risk your users' account to be stolen.

The simplest CSRF attack may look the following:

  • The attacker finds an unprotected <form> on the page, like user update.
  • The attacker crafts an URL which calls the action of the given form, to follow our example, to update the email address of the user.
  • The attacker requests a password reminder and takes over the account.

To protect your users against CSRF attacks, you have to add synchronizer (CSRF) tokens to your form which end up changing state. A CSRF token is:

  • unique per user session,
  • a large random value,
  • generated by a cryptographically secure random number generator.

Once you have this token, you have to add it as a hidden input field in your form, and the server rejects the request action of the token fails validation.

To implement it for Node.js applications, you can use the csurf package:

const cookieParser = require("cookie-parser");
const csrf = require("csurf");
const bodyParser = require("body-parser");
const express = require("express");
// setup route middlewares
const csrfProtection = csrf({ cookie: true });
const parseForm = bodyParser.urlencoded({ extended: false });
// create express app
const app = express();
// parse cookies
// we need this because "cookie" is true in csrfProtection
app.use(cookieParser());
app.get("/form", csrfProtection, function (req, res) {
// pass the csrfToken to the view
res.render("send", { csrfToken: req.csrfToken() });
});
app.post("/process", parseForm, csrfProtection, function (req, res) {
res.send("data is being processed");
});

In the view, you have to pass it to the hidden input field:

<form action="/process" method="POST">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
Favorite color: <input type="text" name="favoriteColor">
<button type="submit">Submit</button>
</form>

To learn more about CSRF prevention, read the CSRF Prevention Cheat Sheet: https://www.owasp.org/index.php/CSRF_Prevention_Cheat_Sheet.

When should I use JWT-based authentication? When should I use session-based authentication?

JWTs or JSON Web Tokens are an open, industry-standard RFC 7519 method for representing claims securely between two parties. Usually, it includes some data on the user, like name and email, as well as two other values, called iat (issued at) and exp (expires at). JWTs are signed by a secret key, but the payloads are (in most cases) not encrypted, so you should not store any sensitive information in JWTs.

Take the following JWT as an example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE1MTYxNDI0NTMsImV4cCI6MTUxNjE0MjUxM30.cC4J0Vd6zvJmD7-BIDCB75LhwYcstePs7xDeFj-ZHAg". It consits of three base64-encoded parts, each delimited with a ..

Let's take a look at them:

  • the first describes the algorithm used and the type,
  • the second is the actual payload,
  • the third part is the signature.

JWTs are usually used for authenticating against a REST API from single page applications. It can simplify how you authenticate against different APIs hosted on different subdomains, that wouldn't work out of the box with cookies because of the same-origin policy.

However, JWTs have to be stored somewhere. Most developers pick local storage or session storage to store them. This approach is potentially opening up your application against XSS attacks - once an attacker manages to run JavaScript on your page, your JWTs are exposed.

Generally speaking, when it comes to security and authentication, you should stick with session-based authentication:

  • cookies are protected from JavaScript access using the HttpOnly flag,
  • cookies can be a lot smaller than JWTs, so you can save bandwidth using them.

You can use JWTs if they are only used for a very short amount of time, like to authenticate a site on a subdomain, download files, or password resets. A scenario like that be seen below:

authenticating using cookies and jwts

What are the best practices for handling secrets, like database passwords?

You should never check your secrets into your version control system, in a plain text form. If you'd like to store secrets in version control, you have to encrypt it. A couple of tools you can use for it:

However, this won't enable you to easily share secrets between projects - if you want to do that, you can adopt Vault. Vault secures, stores, and tightly controls access to tokens, passwords, certificates, API keys, and other secrets in modern computing. Vault handles leasing, key revocation, key rolling, and auditing. You can use Terraform to start up a Vault cluster quickly on AWS.

To better understand Vault, I recommend following the Vault Getting Started tutorial.

Further resources:

Did you like this article? Subscribe to get notified about new ones on engineering management, open-source and the web!
No spam. Ever.