Secure random token in Node.js

Secure random token in Node.js

In this question Erik needs to generate a secure random token in Node.js. There’s the method crypto.randomBytes that generates a random Buffer. However, the base64 encoding in node is not url-safe, it includes / and + instead of – and _. Therefore, the easiest way to generate such token I’ve found is
require(‘crypto’).randomBytes(48, function(ex, buf) {
token = buf.toString(‘base64′).replace(/\//g,’_’).replace(/\+/g,’-‘);
});

Is there a more elegant way?

Solutions/Answers:

Solution 1:

Try crypto.randomBytes():

require('crypto').randomBytes(48, function(err, buffer) {
  var token = buffer.toString('hex');
});

The ‘hex’ encoding works in node v0.6.x or newer.

Solution 2:

Synchronous option in-case if you are not a JS expert like me. Had to spend some time on how to access the inline function variable

var token = crypto.randomBytes(64).toString('hex');

Solution 3:

0. Using nanoid third party library [NEW!]

A tiny, secure, URL-friendly, unique string ID generator for JavaScript

https://github.com/ai/nanoid

const nanoid = require("nanoid");
const id = nanoid(48);

1. Base 64 Encoding with URL and Filename Safe Alphabet

Page 7 of RCF 4648 describes how to encode in base 64 with URL safety.
You can use an existing library like base64url to do the job.

Related:  How to use redis PUBLISH/SUBSCRIBE with nodejs to notify clients when data values change?

The function will be:

var crypto = require('crypto');
var base64url = require('base64url');

/** Sync */
function randomStringAsBase64Url(size) {
  return base64url(crypto.randomBytes(size));
}

Usage example:

randomStringAsBase64Url(20);
// Returns 'AXSGpLVjne_f7w5Xg-fWdoBwbfs' which is 27 characters length.

Note that the returned string length will not match with the size argument (size != final length).

2. Crypto random values from limited set of characters

Beware that with this solution the generated random string is not uniformly distributed.

You can also build a strong random string from a limited set of characters like that:

var crypto = require('crypto');

/** Sync */
function randomString(length, chars) {
  if (!chars) {
    throw new Error('Argument \'chars\' is undefined');
  }

  var charsLength = chars.length;
  if (charsLength > 256) {
    throw new Error('Argument \'chars\' should not have more than 256 characters'
      + ', otherwise unpredictability will be broken');
  }

  var randomBytes = crypto.randomBytes(length);
  var result = new Array(length);

  var cursor = 0;
  for (var i = 0; i < length; i++) {
    cursor += randomBytes[i];
    result[i] = chars[cursor % charsLength];
  }

  return result.join('');
}

/** Sync */
function randomAsciiString(length) {
  return randomString(length,
    'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789');
}

Usage example:

randomAsciiString(20);
// Returns 'rmRptK5niTSey7NlDk5y' which is 20 characters length.

randomString(20, 'ABCDEFG');
// Returns 'CCBAAGDGBBEGBDBECDCE' which is 20 characters length.

Solution 4:

The up-to-date right way to do this asynchronously using ES 2016 standards of async and await (as of Node 7) would be the following:

const crypto = require('crypto');

function generateToken({ stringBase = 'base64', byteLength = 48 } = {}) {
  return new Promise((resolve, reject) => {
    crypto.randomBytes(byteLength, (err, buffer) => {
      if (err) {
        reject(err);
      } else {
        resolve(buffer.toString(stringBase));
      }
    });
  });
}

async function handler(req, res) {
   // default token length
   const newToken = await generateToken();
   console.log('newToken', newToken);

   // pass in parameters - adjust byte length
   const shortToken = await generateToken({byteLength: 20});
   console.log('newToken', shortToken);
}

This works out of the box in Node 7 without any Babel transformations

Related:  How to copy multiple files and keep the folder structure with Gulp

Solution 5:

Random URL and filename string safe (1 liner)

Crypto.randomBytes(48).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');

Solution 6:

With async/await and promisification.

const crypto = require('crypto')
const randomBytes = Util.promisify(crypto.randomBytes)
const plain = (await randomBytes(24)).toString('base64').replace(/\W/g, '')

Generates something similar to VjocVHdFiz5vGHnlnwqJKN0NdeHcz8eM