Can I use the auth:import and auth:export tools with the Firebase Auth Emulator?

Viewed 3120

I would like to preload the firebase auth emulator with my test user accounts whenever it starts up, the same way I do for the Firestore emulator with its import/export options. I tried using auth:import and auth:export while my emulators were running but it connected to our actual dev firebase project, and not the emulators. Is there anyway to run auth:import and auth:export against the auth emulator?

For reference, I am referring to these commands (https://firebase.google.com/docs/cli/auth) and this emulator (https://firebase.google.com/docs/emulator-suite/connect_auth).

5 Answers

The ability to do this has now been added to the firebase tools

The older answers still work and may be useful

https://github.com/firebase/firebase-tools/releases/tag/v9.1.0

Support emulators:export and import for Auth Emulator (#2955).

https://github.com/firebase/firebase-tools/pull/2955

firebase help auth:import
Usage: firebase auth:import [options] [dataFile]

import users into your Firebase project from a data file(.csv or .json)

Options:
  --hash-algo <hashAlgo>               specify the hash algorithm used in password for these accounts
  --hash-key <hashKey>                 specify the key used in hash algorithm
  --salt-separator <saltSeparator>     specify the salt separator which will be appended to salt when verifying password. only used by SCRYPT now.
  --rounds <rounds>                    specify how many rounds for hash calculation.
  --mem-cost <memCost>                 specify the memory cost for firebase scrypt, or cpu/memory cost for standard scrypt
  --parallelization <parallelization>  specify the parallelization for standard scrypt.
  --block-size <blockSize>             specify the block size (normally is 8) for standard scrypt.
  --dk-len <dkLen>                     specify derived key length for standard scrypt.
  --hash-input-order <hashInputOrder>  specify the order of password and salt. Possible values are SALT_FIRST and PASSWORD_FIRST. MD5, SHA1, SHA256, SHA512, HMAC_MD5, HMAC_SHA1, HMAC_SHA256, HMAC_SHA512 support this flag.
  -h, --help                           output usage information

Since firebase confirmed this is not supported I used a cloud function instead to do this. I first exported the users I would want to be loaded on startup into a json format, then I run this cloud function everytime I restart the emulators:

exports.populateAuthUsers = functions.https.onRequest(async (req, res) => {

  try {
    admin.initializeApp();
    
    const users = [
      { uid: "user1", email: "user1@test.com", password: "password" },
      // put all test users you want populated here
    ];

    const results = [];
    for (let user of users) {
      const result = await admin.auth().createUser(user);
      results.push(result);
    }

    return res.status(200).send(results);
  } catch (error) {
    console.error('Error: ', error);
    return res.status(500).send(error);
  }
});

It only takes a couple seconds to run even with a few hundred users. Just make sure to never deploy this to any actual firebase environments! For local use only.

Thanks @alex-egli this was very helpful

Here's my version which is based on yours with 3 small changes

  1. avoid an eslint warning https://eslint.org/docs/rules/no-await-in-loop
    (I don't think your code is bad here and the warning could be muted)

  2. add in a check that this is being run on the emulator

  3. added user display name

exports.populateAuthUsers = functions.https.onRequest(async (req, res) => {
  if (!process.env["FUNCTIONS_EMULATOR"]) {
    return res
      .status(403)
      .send("ACCESS DENIED. This function is ONLY available via an emulator");
  }
  const users = [
    {
      uid: "user1",
      displayName: "one Local Admin",
      email: "one@test.com",
      password: "password",
    },
    {
      uid: "user2",
      displayName: "two Local Admin",
      email: "two@test.com",
      password: "password",
    },
    // put all test users you want populated here
  ];

  const results = [];
  const promises = [];
  for (let user of users) {
    let promise = admin
      .auth()
      .createUser(user)
      .then((result) => {
        return result;
      })
      .catch((error) => {
        return error.message; // continue on errors (eg duplicate users)
      });

    promises.push(promise);
  }
  await Promise.all(promises).then((result) => {
    results.push(result);
    return;
  });
  res.header("Content-type", "application/json");
  return res.status(200).send(JSON.stringify(results));
});

The simplest way to get this done is to pass in the --export-on-exit when you run the emulator, for example: firebase emulators:start --export-on-exit .firebase-emulators, which will get all your test data dumped into the specified directory on exiting the emulator.

You can then just check that into your source code if you want (make sure there's no sensitive data in your emulator), and do firebase emulators:start --import .firebase-emulators to read it every time you start.

https://firebase.google.com/docs/auth/admin/import-users#import_users_with_standard_scrypt_hashed_passwords

admin
  .auth()
  .importUsers(
    [
      {
        uid: 'some-uid',
        email: 'user@example.com',
        // Must be provided in a byte buffer.
        passwordHash: Buffer.from('password-hash'),
        // Must be provided in a byte buffer.
        passwordSalt: Buffer.from('salt'),
      },
    ],
    {
      hash: {
        algorithm: 'STANDARD_SCRYPT',
        memoryCost: 1024,
        parallelization: 16,
        blockSize: 8,
        derivedKeyLength: 64,
      },
    }
  )
  .then((results) => {
    results.errors.forEach((indexedError) => {
      console.log(`Error importing user ${indexedError.index}`);
    });
  })
  .catch((error) => {
    console.log('Error importing users :', error);
  });
Related