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.
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:
input
fields on the webpage<script>alert('ops!')</script>
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:
setInterval
,new Function
,setTimeout
,To learn more about XSS prevention, read the XSS Prevention Cheat Sheet: https://www.owasp.org/index.php/XSS(CrossSite_Scripting)_Prevention_Cheat_Sheet.
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.
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:
<form>
on the page, like user update.action
of the given form
, to follow our example, to update the email address of the user.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:
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 middlewaresconst csrfProtection = csrf({ cookie: true });const parseForm = bodyParser.urlencoded({ extended: false });// create express appconst app = express();// parse cookies// we need this because "cookie" is true in csrfProtectionapp.use(cookieParser());app.get("/form", csrfProtection, function (req, res) {// pass the csrfToken to the viewres.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.
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:
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:
HttpOnly
flag,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:
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.