33

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

5 Answers 5

65

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)
})

Or using ESM and await:

import { read } from "read";

const password = await read({
  prompt: "Password: ",
  silent: true,
  replace: "*" //optional, will print out an asterisk for every typed character 
});
console.log("Your password: " + password);
Sign up to request clarification or add additional context in comments.

8 Comments

github.com/isaacs/read - Had some trouble googling for this module so, I'm dropping a link to it here for your convenience.
Is there a synchronous version?
@Mark: How could there be? stdin is a stream and fires events. Events cannot be processed if a function is waiting to return.
@josh3736: I don't know, perhaps in the same way that readFileSync works??
@mpen best workaround today to avoid the callback is to use async/await BTW: stackoverflow.com/a/71868483/895245
|
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]) } );

2 Comments

You can't verify value. If you press backspace or other touch who isn't alphanumeric.
require('tty').setRawMode() is deprecated.
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
  }
})

1 Comment

Note process.binding("stdio") no longer works in current versions of node
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;
        }
    });
}

2 Comments

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.
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.
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

1 Comment

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)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.