> ## Documentation Index
> Fetch the complete documentation index at: https://docs.theauth.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Lazy password migration

> Verify old password hashes on first login, then replace them with theAuth's own hash. Which algorithms work today and which need a library you provide.

You cannot convert a password hash without the password, so you do not try. Keep the old hash, check it when the user signs in, and write a new hash while you have the plaintext in hand.

```ts theme={"dark"}
import { createDbMigrationStore, createLazyPasswordMigrator } from "@glinr/theauth/migrate";
import bcrypt from "bcryptjs";

const store = await createDbMigrationStore(db);

const passwords = createLazyPasswordMigrator({
  store,
  source: "auth0",
  verifiers: { bcrypt: (password, hash) => bcrypt.compare(password, hash) },
  onLegacyHashAccepted: ({ userId, algorithm }) => {
    metrics.increment("password.rehashed", { algorithm });
  },
});

const result = await passwords.verify(userId, typedPassword);
if (result.success && result.data.valid) {
  // sign the user in
}
```

On a correct password the hash is replaced with theAuth's PBKDF2 hash, a `migrated` record is written, and `onLegacyHashAccepted` runs with the user id and the old algorithm name. Never the password.

If the rehash fails, or your hook throws, the login still succeeds. The old hash stays and the next login tries again.

## What is supported

| Algorithm | Status | Notes |
| - | - | - |
| PBKDF2 with SHA-1, SHA-256, SHA-512 | Built in | Includes Keycloak. Uses WebCrypto, so it runs on Workers, Deno and Bun. |
| scrypt | Built in | Pure JavaScript, checked against the RFC 7914 test vector. Includes Better Auth's layout. Parameters above 256 MB of memory are refused. |
| theAuth PBKDF2 | Built in | Nothing to migrate. |
| bcrypt (Auth0, Clerk, Auth.js) | Bring a verifier | Pass `verifiers.bcrypt`, for example from `bcryptjs`. theAuth does not bundle one. |
| argon2 | Bring a verifier | Pass `verifiers.argon2`. |
| Firebase scrypt | Not supported | Needs your project's signer key and parameters. Those users reset their password. |
| Others (md5, sha1 plain, custom) | Not supported | The importer lists them as unsupported. |

Without a verifier for bcrypt or argon2, `verify` returns an error with code `VERIFIER_MISSING` and the user should be sent through a password reset.

## Safety notes

* Hash comparisons are constant time.
* PBKDF2 iteration counts above 5 million and scrypt memory above 256 MB are refused, so a bad row in an import cannot be used to tie up your server.
* A wrong password never changes the stored hash and never calls the hook.
* Unknown hash formats return `UNKNOWN_HASH` rather than guessing.

## Using it with the username module

The username module checks PBKDF2 only. Call `passwords.verify` yourself in your sign-in route for users that still have an imported hash, then create the session as usual. Users who have already been upgraded verify through the same call, so you can use it for everyone.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.