• Stars
    star
    126
  • Rank 284,543 (Top 6 %)
  • Language
    JavaScript
  • License
    Other
  • Created about 8 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Stateless Cross-Site Request Forgery (CSRF) protection with JWT

Electrode Stateless CSRF

NPM version Build Status Dependency Status

An electrode plugin that enables stateless CSRF protection using JWT in Electrode, Express, Hapi, or Koa 2 applications.

Why do we need this module?

CSRF protection is an important security feature, but in systems which don't have backend session persistence, validation is tricky. Stateless CSRF support addresses this need.

Please see the demo for a sample of using this in a web application with Hapi NodeJS server.

How do we validate requests?

CSRF attacks can be bad when a malicious script can make a request that can perform harmful operations through the user (victim)'s browser, attaching user specific and sensitive data in the cookies.

To prevent it, the technique used by this module is similar to the CSRF double submit cookie prevention technique, and relies on these two restrictions by the browsers:

  1. cross site scripts can't read/modify cookies.
  2. cross site scripts can't set headers.

The double submit cookie prevention technique rely on the fact that a unique token in cookie must match a token attached in a hidden form submit field. Since XSS cannot change cookies, the check prevents CSRF attacks.

Note that the first restriction has some loopholes and thus the double submit cookie technique is not completely secured. See https://www.owasp.org/images/3/32/David_Johansson-Double_Defeat_of_Double-Submit_Cookie.pdf

Double JWT CSRF tokens

For use with XMLHttpRequest and fetch, we extend the technique by using two JWT tokens for validation. One token in the cookies and the other in the HTTP headers. Since XSS cannot set HTTP headers also, it strengthens the security further.

So two JWT CSRF tokens are generated on the server side with the same payload but different types (see below), one for the HTTP header and one for the cookie. This makes two different tokens but uniquely paired with each other by the UUID.

headerPayload = { type: "header", UUID: "12345" };
cookiePayload = { type: "cookie", UUID: "12345" };

When a client makes a request, the JWT tokens must be sent in the cookie and headers, both are channels that cross site scripts have no control over.

Further, we set the cookie to be HTTP Only so any browser that supports it would prevent any scripts from accessing it at all.

On the server side, the tokens are decoded and validated to pair with each other to identify legitimate requests.

If a malicious script somehow manages to alter one of the tokens passed through the cookie or HTTP header, then they will not match. In order to forge a request on the victim's behalf, both restrictions must be circumvented.

Issues

There are some issues with our technique.

  1. We rely on client making all request through AJAX because of the requirement to set HTTP header.

  2. First call has to be a GET to prime the header token. Since the code that use XMLHttpRequest or fetch need to first acquire valid tokens through a non-mutable request like HTTP GET to populate its internal state, so if your first call has to be POST, then it's tricky.

  3. Similar to the cause in #2 above, multiple browser tabs could run into token mismatches, since cookies are shared across tabs but each tab's code keeps its own internal token for the HTTP header.

Issue 1 is the essential of how the technique works so that's just its limitation.

Issue 2 and 3 are tricky, but there are some solutions. See demo for reference.

Install

$ npm install --save electrode-csrf-jwt

Usage and Integration

Browser Integration

To protect your AJAX requests from the browser, your JavaScript code need to first make a GET call to acquire an initial pair of CSRF tokens. The HTTP only cookie token is dropped automatically. Your code has to extract the header token and save it to an internal variable.

In subsequent requests (GET or POST), you have to attach the header token acquired in the HTTP header x-csrf-jwt.

If you receive an error, then you should take the token from the error response and retry one more time.

Full Demo

You can reference a sample demo to use this for your webapp.

Serverside Integration

This module includes a plugin for Hapi (v16 or lower) and middleware for express and koa. They can be used with the following:

Options

First the options. Regardless of which server framework you use, the options remains the same when you pass it to the plugin or middleware.

Required Fields

  • secret: A string or buffer containing either the secret for HMAC algorithms, or the PEM encoded private key for RSA and ECDSA.

Optional Fields

  • cookieName: A string to use as name for setting the cookie token. Default: x-csrf-jwt
  • headerName: A string to use as name for setting the header token. Default: cookieName
  • cookieConfig: An object with extra configs for setting the JWT cookie token. Values set to undefined or null will delete the field from the default cookie config. See the respective server framework for info on what their cookie config should be.
    • path: Cookie path
    • isSecure: Whether cookie is pass secure of not
    • httpOnly: HTTP only.
  • tokenEngine: Experimental A string that specifies the token engine. Either the default "jwt" or "hash".

Optional uuidGen Field

This module by default uses the uuid module. However, it uses crypto.randomBytes, which "uses libuv's threadpool, which can have surprising and negative performance implications for some applications".

If that's an issue, then you can set the uuidGen option as follows to select another UUID generator:

  • "simple" - select a simple one from this module
  • "uuid" - the default: uses uuid
  • function - your own function that returns the ID, which should be a URL safe string

Optional Skip Callbacks

The following should be functions that take the request (or context for Koa) object and return true to skip their respective step for the given request:

  • shouldSkip: Completely skip the CSRF middleware/plugin
  • skipCreate: Skip creating the tokens for the response
  • skipVerify: Skip verifying the incoming tokens

JWT specific optional fields

Others are optional and follow the same usage as jsonwebtoken if the tokenEngine is jwt.

  • algorithm
  • expiresIn
  • notBefore
  • audience
  • subject
  • issuer
  • jwtid
  • subject
  • noTimestamp
  • headers

Electrode Server

electrode-server is a top level wrapper for Hapi. You can use the hapi-plugin in electrode-server by setting your configuration.

Example config/default.js configuration

{
  "plugins": {
    "electrode-csrf-jwt": {
      "options": {
        "secret": "shhhhh",
        "expiresIn": 60,
        shouldSkip: request => {
          // return true to skip CSRF JWT for given request
          return false;
        },
        skipCreate: request => {
          // return true to skip creating CSRF JWT Token for given request
          return false;
        },
        skipVerify: request => {
          // return true to skip verifying CSRF JWT Token for given request
          return false;
        }
      }
    }
  }
}

Express

Example app.js configuration

const csrfMiddleware = require("electrode-csrf-jwt").expressMiddleware;
const express = require("express");

const app = express();

const options = {
  secret: "shhhhh",
  expiresIn: 60,
  shouldSkip: request => {
    // return true to skip CSRF JWT for given request
    return false;
  },
  skipCreate: request => {
    // return true to skip creating CSRF JWT Token for given request
    return false;
  },
  skipVerify: request => {
    // return true to skip verifying CSRF JWT Token for given request
    return false;
  }
};

app.use(csrfMiddleware(options));

Hapi

Example server/index.js configuration

const csrfPlugin = require("electrode-csrf-jwt").register;
const Hapi = require("hapi");

const server = new Hapi.Server();
const options = {
  secret: "shhhhh",
  expiresIn: 60,
  shouldSkip: request => {
    // return true to skip CSRF JWT for given request
    return false;
  },
  skipCreate: request => {
    // return true to skip creating CSRF JWT Token for given request
    return false;
  },
  skipVerify: request => {
    // return true to skip verifying CSRF JWT Token for given request
    return false;
  }
};

server.register({ register: csrfPlugin, options }, err => {
  if (err) {
    throw err;
  }
});

Koa 2

Example app.js configuration

const csrfMiddleware = require("electrode-csrf-jwt").koaMiddleware;
const Koa = require("koa");

const app = new Koa();

const options = {
  secret: "shhhhh",
  expiresIn: 60,
  shouldSkip: context => {
    // return true to skip CSRF JWT for given context
    return false;
  },
  skipCreate: context => {
    // return true to skip creating CSRF JWT Token for given context
    return false;
  },
  skipVerify: context => {
    // return true to skip verifying CSRF JWT Token for given context
    return false;
  }
};

app.use(csrfMiddleware(options));

Fastify

Please register fastify-cookie plugin before electrode-csrf-jwt to add cookie support with fastify.

Example server.js configuration

const csrfPlugin = require("electrode-csrf-jwt").fastify;
const Fastify = require("fastify");
const fastifyCookie = require("fastify-cookie");

csrfPlugin[Symbol.for("skip-override")] = true;

const server = Fastify();
const options = {
  secret: "shhhhh",
  expiresIn: 60,
  shouldSkip: request => {
    // return true to skip CSRF JWT for given request
    return false;
  },
  skipCreate: request => {
    // return true to skip creating CSRF JWT Token for given request
    return false;
  },
  skipVerify: request => {
    // return true to skip verifying CSRF JWT Token for given request
    return false;
  }
};

server.register(fastifyCookie).register(csrfPlugin, options);
server.listen(3000, err => {
  if (err) throw err;
  console.log(`Server listening at http://localhost:${fastify.server.address().port}`);
});

HTTPS and cookies

When running in HTTPS, you will need to specify the cookie with secure=true. Use the cookieConfig option

{
   "cookieConfig": {
     "isSecure": true
   }
}

Client-side fetch

When doing client-side fetch to the server, it is preferable to use electrode-fetch.
Electrode-fetch will look for the x-csrf-jwt header from responses and use it as the new JWT token on subsequent fetches.
If you use your own fetch function, you will have to handle this yourself.

Built with ❀️ by Team Electrode @WalmartLabs.

More Repositories

1

electrode

Web applications with node.js and React
HTML
2,101
star
2

electrode-native

A platform to ease integration&delivery of React Native apps in existing mobile applications
TypeScript
725
star
3

electrode-io.github.io

The public website of the Electrode platform
HTML
336
star
4

electrode-react-ssr-caching

Optimize React SSR with profiling and component caching
JavaScript
316
star
5

electrode-explorer

An Electrode application that showcases all of your components in a live demo
JavaScript
254
star
6

electrode-electrify

Electrify is an webpack visualizer for analyzing webpack bundles.
JavaScript
230
star
7

electrode-server

Electrode's configurable web server using Hapi.js atop Node.js
JavaScript
224
star
8

electrode-ota-server

Electrode Over The Air Server for hot deployment of React Native and Cordova mobile apps
JavaScript
204
star
9

above-the-fold-only-server-render

An Electrode component for optionally skipping server side render of components outside of Above the fold
JavaScript
117
star
10

electrode-confippet

node.js environment aware application configuration
TypeScript
108
star
11

isomorphic-loader

Webpack isomorphic loader tools to make Node require handle files like images for Server Side Rendering (SSR)
JavaScript
68
star
12

electrode-archetype-njs-module-dev

A WalmartLabs flavored NodeJS Module archetype
JavaScript
67
star
13

electrode-bundle-analyzer

Analyze your webpack deduped and minified bundle JS file.
JavaScript
66
star
14

electrode-webpack-reporter

A HTML based reporter for webpack dev server
JavaScript
59
star
15

electrode-gulp-helper

Helper functions for using gulp
JavaScript
59
star
16

electrode-check-dependencies

An Electrode module to verify component dependencies against a list
JavaScript
58
star
17

electrode-docgen

A custom metadata extractor and documentation generator for the Electrode framework
JavaScript
57
star
18

fynpo

πŸ› οΈπŸ“¦ a node.js monorepo manager
JavaScript
55
star
19

electrode-static-paths

Electrode server decor to serve static files
JavaScript
55
star
20

react-native-electrode-bridge

Electrode Native - Bridge
Java
44
star
21

xarc-run

npm run scripts concurrently and serially, and more.
JavaScript
42
star
22

memcache

Node.js memcached client with the most efficient ASCII protocol parser
TypeScript
37
star
23

electrode-ota-desktop

Electrode OTA Desktop Client
JavaScript
22
star
24

electrode-ota-ui

[Deprecated for electrode-ota-server/electrode-ota-ui] Electrode OTA Web/Client UI
JavaScript
17
star
25

ern-navigation

Electrode Native solution for React Native navigation
JavaScript
12
star
26

movies-reloaded-miniapp

Our new movie miniapp built on the Electrode Native Navigation.
JavaScript
10
star
27

car-buying-instructions

JavaScript
9
star
28

movielist-miniapp

Electrode Native - Movie List MiniApp (Getting Started)
Objective-C
8
star
29

fastify-server

Electrode using Fastify
TypeScript
6
star
30

moviedetails-miniapp

Electrode Native - Movie Details MiniApp (Getting Started)
JavaScript
6
star
31

electrode-keepalive

node.js HTTP agent with customized keep alive
JavaScript
6
star
32

kax

TypeScript
5
star
33

react-native-livebundle

LiveBundle Native Module
Java
3
star
34

livebundle

LiveBundle CLI
TypeScript
3
star
35

electrode-native-starter-manifest

Electrode Native - Starter Manifest
3
star
36

react-native-ernnavigation-api

Electrode Native - Navigation API (Getting Started)
Swift
3
star
37

electrode-native-manifest

Electrode Native - Master Manifest
Java
3
star
38

electrode-native-showcaseapp-android

Native application that showcases electrode native MiniApps and APIs.
Java
2
star
39

electrode-native-sample-cauldron

Sample Electrode Native Cauldron
2
star
40

fynpo-old

Supplement tools for using fyn with lerna
JavaScript
2
star
41

car-buying-service

mock service for car buying app
JavaScript
2
star
42

car-buying

A sample car buying app
JavaScript
2
star
43

ern-bundle-store

Electrode Native Bundle Store Server
TypeScript
2
star
44

electrode-native-website

The site and docs for Electrode Native
JavaScript
2
star
45

react-native-ernmovie-api

Electrode Native - Movie API (Getting Started)
Swift
2
star
46

ernnavigation-api-impl-native

Native implementation of ernnavigation-api
Objective-C
2
star
47

electrode-demo-app

Demo application generated by Electrode platform unmodified
JavaScript
1
star
48

react-native-ernmovie-api-impl

Objective-C
1
star
49

ern-sourcemap-store

Electrode Native sourcemap store server
TypeScript
1
star
50

electrode-native-binarystore

Electrode Native - Binary Store
JavaScript
1
star
51

ern-container-transformer-xcframework

Electrode Native XCFramework Container Transformer
Shell
1
star
52

ern-container-publisher-maven

Electrode Native Maven Container Publisher
TypeScript
1
star
53

resolve-alias

set virtual module aliases for resolve and require
JavaScript
1
star
54

ern-base-composite-starter

Electrode Native Base Composite Starter
JavaScript
1
star
55

electrode-native-showcaseapp-ios

Swift
1
star
56

react-native-ernmovie-api-impl-js

JavaScript
1
star