33

How to get password from input using node.js? Which means you should not output password entered in console.

Alfred
  • 60,935
  • 33
  • 147
  • 186

5 Answers5

62

You can use the read module (disclosure: written by me) for this:

In your shell:

npm install read

Then in your JS:

var read = require('read')
read({ prompt: 'Password: ', silent: true }, function(er, password) {
  console.log('Your password is: %s', password)
})
xehpuk
  • 7,814
  • 3
  • 30
  • 54
isaacs
  • 16,656
  • 6
  • 41
  • 31
16

Update 2015 Dec 13: readline has replaced process.stdin and node_stdio was removed from Node 0.5.10.

var BACKSPACE = String.fromCharCode(127);


// Probably should use readline
// https://nodejs.org/api/readline.html
function getPassword(prompt, callback) {
    if (prompt) {
      process.stdout.write(prompt);
    }

    var stdin = process.stdin;
    stdin.resume();
    stdin.setRawMode(true);
    stdin.resume();
    stdin.setEncoding('utf8');

    var password = '';
    stdin.on('data', function (ch) {
        ch = ch.toString('utf8');

        switch (ch) {
        case "\n":
        case "\r":
        case "\u0004":
            // They've finished typing their password
            process.stdout.write('\n');
            stdin.setRawMode(false);
            stdin.pause();
            callback(false, password);
            break;
        case "\u0003":
            // Ctrl-C
            callback(true);
            break;
        case BACKSPACE:
            password = password.slice(0, password.length - 1);
            process.stdout.clearLine();
            process.stdout.cursorTo(0);
            process.stdout.write(prompt);
            process.stdout.write(password.split('').map(function () {
              return '*';
            }).join(''));
            break;
        default:
            // More passsword characters
            process.stdout.write('*');
            password += ch;
            break;
        }
    });
}

getPassword('Password: ', (ok, password) => { console.log([ok, password]) } );
Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
mikemaccana
  • 110,530
  • 99
  • 389
  • 494
5

To do this I found this excellent Google Group post

Which contains the following snippet:

var stdin = process.openStdin()
  , stdio = process.binding("stdio")
stdio.setRawMode()

var password = ""
stdin.on("data", function (c) {
  c = c + ""
  switch (c) {
    case "\n": case "\r": case "\u0004":
      stdio.setRawMode(false)
      console.log("you entered: "+password)
      stdin.pause()
      break
    case "\u0003":
      process.exit()
      break
    default:
      password += c
      break
  }
})
Alfred
  • 60,935
  • 33
  • 147
  • 186
4

Here is my tweaked version of nailer's from above, updated to get a callback and for node 0.8 usage:

/**
 * Get a password from stdin.
 *
 * Adapted from <http://stackoverflow.com/a/10357818/122384>.
 *
 * @param prompt {String} Optional prompt. Default 'Password: '.
 * @param callback {Function} `function (cancelled, password)` where
 *      `cancelled` is true if the user aborted (Ctrl+C).
 *
 * Limitations: Not sure if backspace is handled properly.
 */
function getPassword(prompt, callback) {
    if (callback === undefined) {
        callback = prompt;
        prompt = undefined;
    }
    if (prompt === undefined) {
        prompt = 'Password: ';
    }
    if (prompt) {
        process.stdout.write(prompt);
    }

    var stdin = process.stdin;
    stdin.resume();
    stdin.setRawMode(true);
    stdin.resume();
    stdin.setEncoding('utf8');

    var password = '';
    stdin.on('data', function (ch) {
        ch = ch + "";

        switch (ch) {
        case "\n":
        case "\r":
        case "\u0004":
            // They've finished typing their password
            process.stdout.write('\n');
            stdin.setRawMode(false);
            stdin.pause();
            callback(false, password);
            break;
        case "\u0003":
            // Ctrl-C
            callback(true);
            break;
        default:
            // More passsword characters
            process.stdout.write('*');
            password += ch;
            break;
        }
    });
}
Trent Mick
  • 199
  • 1
  • 6
  • You can handle backspace with an extra case statement for `"u007F"`. If the password so far is non-empty, you can use `process.stdout.write('\033[<1>D')` to move the cursor back one column; after that you can write a space, then move back again. –  Jun 26 '15 at 08:02
  • I updated this to work with backspace and merged it with the original accepted answer and added a link to a repo that I plan to continue to improve. – coolaj86 Dec 13 '15 at 10:46
2

How to use read without a callback

With async/await, we can get rid of the annoying callback with the following standard pattern:

const readCb = require('read')

async function read(opts) {
  return new Promise((resolve, reject) => {
    readCb(opts, (err, line) => {
      if (err) {
        reject(err)
      } else {
        resolve(line)
      }
    })
  })
}

;(async () => {
  const password = await read({ prompt: 'Password: ', silent: true })
  console.log(password)
})()

The annoyance is that then you have to propagate async/await to the entire call stack, but it is generally the way to go, as it clearly marks what is async or not.

Tested on "read": "1.0.7", Node.js v14.17.0.

TODO how to prevent EAGAIN error if you try to use stdin again later with fs.readFileSync(0)?

Both read and https://stackoverflow.com/a/10357818/895245 cause future attempts to read from stdin with fs.readFileSync(0) to break with EAGAIN. Not sure how to fix that. Reproduction:

const fs = require('fs')
const readCb = require('read')

async function read(opts) {
  return new Promise((resolve, reject) => {
    readCb(opts, (err, line) => {
      resolve([err, line])
    })
  })
}

;(async () => {
  const [err, password] = await read({ prompt: 'Password: ', silent: true })
  console.log(password)
  console.log(fs.readFileSync(0).toString())
})()

or:

#!/usr/bin/env node

const fs = require('fs')

var BACKSPACE = String.fromCharCode(127);

// Probably should use readline
// https://nodejs.org/api/readline.html
function getPassword(prompt, callback) {
    if (prompt) {
      process.stdout.write(prompt);
    }

    var stdin = process.stdin;
    stdin.resume();
    stdin.setRawMode(true);
    stdin.resume();
    stdin.setEncoding('utf8');

    var password = '';
    stdin.on('data', function (ch) {
        ch = ch.toString('utf8');

        switch (ch) {
        case "\n":
        case "\r":
        case "\u0004":
            // They've finished typing their password
            process.stdout.write('\n');
            stdin.setRawMode(false);
            stdin.pause();
            callback(false, password);
            break;
        case "\u0003":
            // Ctrl-C
            callback(true);
            break;
        case BACKSPACE:
            password = password.slice(0, password.length - 1);
            process.stdout.clearLine();
            process.stdout.cursorTo(0);
            process.stdout.write(prompt);
            process.stdout.write(password.split('').map(function () {
              return '*';
            }).join(''));
            break;
        default:
            // More passsword characters
            process.stdout.write('*');
            password += ch;
            break;
        }
    });
}

async function read(opts) {
  return new Promise((resolve, reject) => {
    getPassword(opts, (err, line) => {
      resolve([err, line])
    })
  })
}

;(async () => {
  const [err, password] = await read('Password: ')
  console.log(password)
  console.log(fs.readFileSync(0).toString())
})()

Error:

fs.js:614
  handleErrorFromBinding(ctx);
  ^

Error: EAGAIN: resource temporarily unavailable, read
    at Object.readSync (fs.js:614:3)
    at tryReadSync (fs.js:383:20)
    at Object.readFileSync (fs.js:420:19)
    at /home/ciro/test/main.js:70:18
    at processTicksAndRejections (internal/process/task_queues.js:95:5) {
  errno: -11,
  syscall: 'read',
  code: 'EAGAIN'
}

Asked at: https://github.com/npm/read/issues/39

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
  • 1
    You could `reject()` if there's an error instead of returning a tuple. Your error might be from forgetting to close some pipe. e.g. you have to `close()` readline interfaces ([eg](https://stackoverflow.com/a/50890409/65387)) – mpen Apr 16 '22 at 01:39