16

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

Alex Egli
  • 1,884
  • 2
  • 24
  • 43

5 Answers5

5

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
Sean Burlington
  • 873
  • 10
  • 13
  • 4
    How do you actually implement auth imports into emulator now that the functionality exists? I cannot seem to find any clear docs/examples. For instance, currently to import Firestore DB I have a previous DB export I pull in using: `firebase emulators:start --import=./backups/$DB_ENV/db` What would I add to this to pull in for example an auth export stored in file: `auth.json` also? – shaone Feb 15 '21 at 20:13
  • I haven't tried this myself yet but I've now added the command help above – Sean Burlington Feb 17 '21 at 11:51
  • 3
    Thanks Sean. To solve my particular auth import issue I ended up running `firebase emulators:export ./backups/emulator` This produces the new updated metadata file and folder structure which emulator is looking for during imports—this includes an `auth_export` folder if you're on the latest version. So now I simply mirror that file/folder structure and can migrate live data into emulator. Happy days! – shaone Feb 17 '21 at 12:50
  • @shaone your comment helped a lot. Thanks. – Soorya Aug 30 '22 at 14:22
  • @shaone did you ever find solution for this? – Jeff Voss Dec 20 '22 at 17:50
3

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.

Alex Egli
  • 1,884
  • 2
  • 24
  • 43
  • How do you execute this? – Luis Ruiz Figueroa Mar 19 '21 at 15:21
  • I would recommend using the built in tooling provided with the auth emulator now, as mentioned by Sean Burlington. If you still prefer to use the cloud function then you execute it by running a normal GET request against your cloud function. I generally just ctrl+click on the url for this cloud function posted in my terminal whenever my emulators start up. All cloud function urls should be listed when you start up your emulators. – Alex Egli Mar 19 '21 at 15:28
3

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

Sean Burlington
  • 873
  • 10
  • 13
  • 1
    I really like your emulator check, I'm going to add that to my local code. I intentionally add my users in serial because in my case I'm adding a few hundred users and I don't want to overload the number of available connections or local resources. If you're adding less than a hundred then doing it in parallel the way you have here is probably best. – Alex Egli Nov 24 '20 at 18:43
  • How to execute this action? In some script or from my application code? – Luis Ruiz Figueroa Mar 19 '21 at 14:43
  • This example used cloud functions https://firebase.google.com/docs/functions/get-started But note that you can now import and export auth data via the emulators directly as https://stackoverflow.com/a/65754945/1537692 – Sean Burlington Mar 26 '21 at 14:44
  • Any reason this would still be updating production? What firebase-admin version is needed to pick up emulators? All other functionality is working with updating the emulators – user2521295 Apr 08 '21 at 01:51
3

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.

yelsayed
  • 5,236
  • 3
  • 27
  • 38
-1

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