<!-- llms.txt: https://workos.com/llms.txt -->
<!-- feedback.md: https://workos.com/docs/feedback.md -->

# Export users from WorkOS

## Introduction

If you're moving your users from WorkOS to another system, you can take their password hashes with you, so they can keep signing in with their existing passwords. In this guide, we'll walk through how to request an export of your users from WorkOS, what the export contains, and how your new system can verify each password hash format.

***

## (1) Requesting an export

WorkOS deliberately doesn't offer an API or dashboard export of password hashes. Password hashes are sensitive credentials, so for security reasons they're only released through a request to WorkOS support and delivered securely.

To request an export, email [support@workos.com](mailto:support@workos.com) and include:

1. The environments you need exported, such as Production or Staging.
2. A secure email address to receive the export.

### Exporting users without password hashes

If you only need your users' profile data, you can export it yourself from the [WorkOS Dashboard](https://dashboard.workos.com/). Go to **Users** in the environment you want to export, and click the download icon (**Export CSV**) above the users table. Under **Users created**, choose **All time** to include every user, then click **Export CSV**. When the export is ready, click **Download**. Any search or filters applied to the table narrow the export to the matching users.

This CSV includes profile fields such as email, name and email verification status. It never includes password hashes.

***

## (2) Receiving the export

WorkOS sends the export as a CSV file through a secure file-transfer service, to the email address you provided.

The CSV has the following columns:

| Column          | Description                                                                        |
| --------------- | ---------------------------------------------------------------------------------- |
| `email`         | The user's email address.                                                          |
| `password_hash` | The user's password hash, in one of the [formats below](#3-password-hash-formats). |
| `first_name`    | The user's first, or given name.                                                   |
| `last_name`     | The user's last, or family name.                                                   |

```txt title="Example export"
email,password_hash,first_name,last_name
marcelina.davis@example.com,$2b$10$EXAMPLEsaltEXAMPLEsaltEXAMPLEdigestEXAMPLEdigest00000,Marcelina,Davis
```

Parse the file with a CSV library rather than splitting lines on commas: some password hashes contain commas in their parameters.

Users who have never set a password, such as those who only sign in with SSO, a social provider or Magic Auth, have no password hash to export.

***

## (3) Password hash formats

A user's password hash is stored in the format it was set or imported in. Most can be verified directly with a standard library for the algorithm. Imported `ssha`, `ssha256` and some `pbkdf2` hashes were wrapped in bcrypt at import, so your new system needs to verify them as described below.

| `password_hash` starts with                                 | Where the password came from                                  | Verify with                                     |
| ----------------------------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------- |
| `$2b$10$`                                                   | Set in WorkOS                                                 | bcrypt                                          |
| `$2a$`, `$2b$` or `$2y$`                                    | Imported as `bcrypt`                                          | bcrypt                                          |
| `$scrypt$`, `$firebase-scrypt$`, `$argon2id$` or `$pbkdf2$` | Imported as `scrypt`, `firebase-scrypt`, `argon2` or `pbkdf2` | The original algorithm                          |
| `$bcrypt-pbkdf2-sha1$` or `$bcrypt-pbkdf2-sha256$`          | Imported as `pbkdf2`, then re-hashed with bcrypt              | bcrypt over the hex-encoded PBKDF2 key          |
| `$bcrypt-ssha$` or `$bcrypt-ssha256$`                       | Imported as `ssha` or `ssha256`                               | bcrypt over a hex-encoded SHA1 or SHA256 digest |

The examples in this section are illustrative and don't correspond to real passwords.

### Passwords set in WorkOS

Passwords that users set in WorkOS, for example when signing up or resetting their password, are hashed with bcrypt at cost 10 and stored in modular crypt format: `$2b$10$<salt><digest>`.

```txt
$2b$10$EXAMPLEsaltEXAMPLEsaltEXAMPLEdigestEXAMPLEdigest00000
```

### Imported bcrypt hashes

Imported bcrypt hashes are kept exactly as you provided them, so they may use the `$2a$`, `$2b$` or `$2y$` prefix and a cost other than 10.

```txt
$2y$12$EXAMPLEsaltEXAMPLEsaltEXAMPLEdigestEXAMPLEdigest00000
```

Any bcrypt library can verify these. If yours doesn't accept the `$2y$` prefix, replace it with `$2b$`; the two are interchangeable for verification.

### Imported `scrypt`, `firebase-scrypt`, `argon2` and `pbkdf2` hashes

Passwords imported with `scrypt`, `firebase-scrypt`, `argon2` or `pbkdf2` stay as the [PHC strings](https://github.com/P-H-C/phc-string-format/blob/5f1e4ec633845d43776849f503f8ce8314b5290c/phc-sf-spec.md) you imported: `$<id>$<params>$<base64 salt>$<base64 digest>`, with a `v=` version segment after the ID for some algorithms. Argon2 hashes may use the `argon2id`, `argon2i` or `argon2d` ID. Salts and digests are base64 encoded without padding.

```txt
$scrypt$v=1$n=16384,r=8,p=1,kl=64$ZXhhbXBsZXNhbHQ$ZXhhbXBsZWRpZ2VzdA
$argon2id$v=19$m=65536,t=3,p=4$ZXhhbXBsZXNhbHQ$ZXhhbXBsZWRpZ2VzdA
```

The parameters are the ones described in [Importing passwords](https://workos.com/docs/migrate/other-services#importing-passwords). For `firebase-scrypt`, the string also carries your Firebase project's signer key and salt separator in the `sk` and `ss` parameters, as described in the [Firebase migration guide](https://workos.com/docs/migrate/firebase).

### Imported `pbkdf2` hashes re-hashed with bcrypt

`pbkdf2` hashes that use the `sha1` digest, or the `sha256` digest below the minimum iteration count, were [re-hashed with bcrypt at import](https://workos.com/docs/migrate/other-services#importing-passwords). They're stored as PHC strings with the ID `bcrypt-pbkdf2-sha1` or `bcrypt-pbkdf2-sha256`:

```txt
$bcrypt-pbkdf2-sha256$v=1$i=1000,kl=32$ZXhhbXBsZXNhbHQ$JDJiJDEwJEVYQU1QTEVzYWx0RVhBTVBMRXNhbHRFWEFNUExFZGlnZXN0RVhBTVBMRWRpZ2VzdDAwMDAw
```

- The salt field is the original PBKDF2 salt.
- The `i` parameter is the original iteration count, and `kl` is the derived key length in bytes.
- The hash field is a complete bcrypt hash (`$2b$10$…`), base64 encoded.

To verify a password, derive the key with PBKDF2 using the digest named in the ID, the decoded salt, `i` iterations and a key length of `kl`. Encode the derived key as lowercase hex, then check that hex string against the decoded bcrypt hash with a bcrypt library.

### Imported `ssha` and `ssha256` hashes

Imported `ssha` and `ssha256` hashes were wrapped in bcrypt at import. They're stored as PHC strings with the ID `bcrypt-ssha` or `bcrypt-ssha256`:

```txt
$bcrypt-ssha$v=1$ZXhhbXBsZXNhbHQ$JDJiJDEwJEVYQU1QTEVzYWx0RVhBTVBMRXNhbHRFWEFNUExFZGlnZXN0RVhBTVBMRWRpZ2VzdDAwMDAw
$bcrypt-ssha256$v=1$sp=prefix$ZXhhbXBsZXNhbHQ$JDJiJDEwJEVYQU1QTEVzYWx0RVhBTVBMRXNhbHRFWEFNUExFZGlnZXN0RVhBTVBMRWRpZ2VzdDAwMDAw
```

- The salt field is the original `ssha` or `ssha256` salt.
- The `sp` parameter records the salt position for `bcrypt-ssha256`. `sp=prefix` means the salt comes before the password. If `sp` is absent, the salt is a suffix and comes after the password. `bcrypt-ssha` hashes never have `sp`; their salt is always a suffix.
- The hash field is a complete bcrypt hash (`$2b$10$…`), base64 encoded.

These are not plain `ssha` or `ssha256` hashes, and they can't be converted back to that format. Your new system must verify them as bcrypt over the hex-encoded SHA1 or SHA256 digest, with the salt position following `sp`:

| ID               | `sp`     | Verify as                              |
| ---------------- | -------- | -------------------------------------- |
| `bcrypt-ssha`    | Absent   | `bcrypt(hex(sha1(password + salt)))`   |
| `bcrypt-ssha256` | Absent   | `bcrypt(hex(sha256(password + salt)))` |
| `bcrypt-ssha256` | `prefix` | `bcrypt(hex(sha256(salt + password)))` |

To verify a password:

1. Base64-decode the salt and hash fields of the PHC string.
2. Compute the SHA1 or SHA256 digest of the `UTF-8` encoded password and the raw salt bytes, in the order set by `sp`.
3. Encode the digest as lowercase hex.
4. Check that hex string against the decoded bcrypt hash with a bcrypt library.

If your new system can't run custom verification like this, these users will need to reset their passwords.

***

## Users whose passwords can't be moved

If your new system can't verify one of these formats, or you'd rather not move password hashes, those users can reset their passwords instead. While your users still sign in with WorkOS, you can trigger a reset with the WorkOS [Password Reset API](https://workos.com/docs/reference/authkit/password-reset). A password reset in WorkOS stores a standard `$2b$10$` bcrypt hash, which you can then include in a new export. After you switch, use your new system's password reset flow.

If you have any questions about exporting your users, reach out to [support@workos.com](mailto:support@workos.com).
