[GH-ISSUE #5016] Auth0 migration: built‑in verification for migrated credential hashes ($2a/$2b bcrypt) fails #10138

Closed
opened 2026-04-13 06:04:31 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @yogeshdecodes on GitHub (Oct 1, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/5016

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Problem: Following the Auth0 migration guide, we imported users credential with their original password Bcrypt hashes($2b) but better-auth don’t verify out-of-the-box; login returns error hex string expected got undefined. To fix this custom emailAndPassword.password.verify logic is needed, which works but then password created using better-auth failed as it uses scrypt hashing algorithm.

  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    password: {
      verify: async ({
        hash,
        password,
      }: {
        hash: string
        password: string
      }) => {
        if (!hash) return false
        if (hash.startsWith('$2a$') || hash.startsWith('$2b$')) {
          try {
            return await bcrypt.compare(password, hash)
          } catch {
            return false
          }
        }
        return false
      },
    },

Current vs. Expected behavior

Example data:
auth0User.password_hash: $2b$10$35ei8G8tZgZP.qNJi6d6mecPCTqtfIjtmy4P2/JwlFK36xOYHbKHe

Current:
better-auth don’t verify out-of-the-box auth0 hashed password; On login returns error hex string expected got undefined

Expected:
Better Auth should verifies migrated Auth0 users without any custom code. It will be helpful if better-auth internally check if hash starts with $2b or $2a then use bcrypt otherwise scrypt.

What version of Better Auth are you using?

1.3.4

System info

better-auth : 1.3.4
Node: 20
Next: 15

Which area(s) are affected? (Select all that apply)

Backend, Documentation

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  emailAndPassword: {  
    enabled: true,
requireEmailVerification: true,
  },
});

Additional context

No response

Originally created by @yogeshdecodes on GitHub (Oct 1, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/5016 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce **Problem:** Following the [Auth0 migration guide](https://www.better-auth.com/docs/guides/auth0-migration-guide), we imported users credential with their original password Bcrypt hashes($2b) but better-auth don’t verify out-of-the-box; login returns error `hex string expected got undefined`. To fix this custom emailAndPassword.password.verify logic is needed, which works but then password created using better-auth failed as it uses scrypt hashing algorithm. ``` emailAndPassword: { enabled: true, requireEmailVerification: true, password: { verify: async ({ hash, password, }: { hash: string password: string }) => { if (!hash) return false if (hash.startsWith('$2a$') || hash.startsWith('$2b$')) { try { return await bcrypt.compare(password, hash) } catch { return false } } return false }, }, ``` ### Current vs. Expected behavior Example data: auth0User.password_hash: $2b$10$35ei8G8tZgZP.qNJi6d6mecPCTqtfIjtmy4P2/JwlFK36xOYHbKHe Current: better-auth don’t verify out-of-the-box auth0 hashed password; On login returns error `hex string expected got undefined` Expected: Better Auth should verifies migrated Auth0 users without any custom code. It will be helpful if better-auth internally check if hash starts with $2b or $2a then use bcrypt otherwise scrypt. ### What version of Better Auth are you using? 1.3.4 ### System info ```bash better-auth : 1.3.4 Node: 20 Next: 15 ``` ### Which area(s) are affected? (Select all that apply) Backend, Documentation ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true, requireEmailVerification: true, }, }); ``` ### Additional context _No response_
GiteaMirror added the lockedbug labels 2026-04-13 06:04:31 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Oct 1, 2025):

Hi @yogeshdecodes! I'm Dosu and I’m helping the better-auth team.

Better Auth's core password verification logic is designed to auto-detect bcrypt hashes (prefixes like $2a$ or $2b$) and verify them out-of-the-box, unless you override the verify function in your config. If you set a custom verify function, you must handle all hash types yourself—this disables the built-in detection for both bcrypt and scrypt.

The error you're seeing (hex string expected got undefined) typically happens when the hash format doesn't match what Better Auth expects, or when custom logic doesn't handle all possible hash types. For migrated users with bcrypt hashes, you can either:

  • Remove the custom verify function and let Better Auth handle both bcrypt and scrypt hashes automatically (as long as your hashes use standard prefixes).
  • If you need custom logic, ensure your verify function supports both bcrypt and scrypt formats, using prefix checks.

If you want migrated users to transition to scrypt, consider a password reset flow so new passwords are hashed in the native format. This approach is also recommended for migrations from other platforms like Supabase, which use bcrypt hashes and encounter similar issues with Better Auth's default scrypt expectation [source].

No recent updates or pull requests have changed this behavior, and the documentation could be clearer about how custom verification logic interacts with built-in hash detection.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3356169597 --> @dosubot[bot] commented on GitHub (Oct 1, 2025): <!-- Greeting --> Hi @yogeshdecodes! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> Better Auth's core password verification logic is designed to auto-detect bcrypt hashes (prefixes like `$2a$` or `$2b$`) and verify them out-of-the-box, unless you override the `verify` function in your config. If you set a custom `verify` function, you must handle all hash types yourself—this disables the built-in detection for both bcrypt and scrypt. The error you're seeing (`hex string expected got undefined`) typically happens when the hash format doesn't match what Better Auth expects, or when custom logic doesn't handle all possible hash types. For migrated users with bcrypt hashes, you can either: - Remove the custom `verify` function and let Better Auth handle both bcrypt and scrypt hashes automatically (as long as your hashes use standard prefixes). - If you need custom logic, ensure your `verify` function supports both bcrypt and scrypt formats, using prefix checks. If you want migrated users to transition to scrypt, consider a password reset flow so new passwords are hashed in the native format. This approach is also recommended for migrations from other platforms like Supabase, which use bcrypt hashes and encounter similar issues with Better Auth's default scrypt expectation [[source](https://github.com/better-auth/better-auth/issues/4762)]. No recent updates or pull requests have changed this behavior, and the documentation could be clearer about how custom verification logic interacts with built-in hash detection. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/5ef497e9-c6ef-47e2-a958-2e4c20d219b6?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/5ef497e9-c6ef-47e2-a958-2e4c20d219b6?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/5ef497e9-c6ef-47e2-a958-2e4c20d219b6?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/5ef497e9-c6ef-47e2-a958-2e4c20d219b6?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/5ef497e9-c6ef-47e2-a958-2e4c20d219b6?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/5ef497e9-c6ef-47e2-a958-2e4c20d219b6?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/5ef497e9-c6ef-47e2-a958-2e4c20d219b6?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/5016)
Author
Owner

@yogeshdecodes commented on GitHub (Oct 1, 2025):

@dosu
In our case, without any custom verify function, Better Auth did not successfully verify migrated credential users:
We imported Auth0 users with their original hashes stored in account.password.
Bcrypt $2a$/2b failed verification with “hex string expected got undefined”.
Only after adding a custom emailAndPassword.password.verify (handling $2a$/2b via bcrypt did authenticate.

<!-- gh-comment-id:3356201044 --> @yogeshdecodes commented on GitHub (Oct 1, 2025): @dosu In our case, without any custom verify function, Better Auth did not successfully verify migrated credential users: We imported Auth0 users with their original hashes stored in account.password. Bcrypt $2a$/$2b$ failed verification with “hex string expected got undefined”. Only after adding a custom emailAndPassword.password.verify (handling $2a$/$2b$ via bcrypt did authenticate.
Author
Owner

@dosubot[bot] commented on GitHub (Oct 1, 2025):

This issue happens because Better Auth expects password hashes in a specific format—usually scrypt—so when you import bcrypt hashes from Auth0, the built-in verification logic doesn't recognize them and throws errors like “hex string expected got undefined.” While the code is designed to auto-detect bcrypt hashes by their $2a$/2b prefix, successful verification depends on the exact storage format and encoding in your database. If the hash doesn't strictly match what Better Auth expects, or if the field naming/encoding is off, it defaults to scrypt and fails with migrated bcrypt hashes.

The recommended approach is to either use a custom verify function (as you did), or trigger a password reset flow for migrated users so new passwords are stored in the native scrypt format. This is a common migration pain point for platforms moving from bcrypt to Better Auth [source].

Improving documentation around hash import requirements and expected formats would help clarify this for future migrations.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3356208433 --> @dosubot[bot] commented on GitHub (Oct 1, 2025): <!-- Answer --> This issue happens because Better Auth expects password hashes in a specific format—usually scrypt—so when you import bcrypt hashes from Auth0, the built-in verification logic doesn't recognize them and throws errors like “hex string expected got undefined.” While the code is designed to auto-detect bcrypt hashes by their $2a$/$2b$ prefix, successful verification depends on the exact storage format and encoding in your database. If the hash doesn't strictly match what Better Auth expects, or if the field naming/encoding is off, it defaults to scrypt and fails with migrated bcrypt hashes. The recommended approach is to either use a custom verify function (as you did), or trigger a password reset flow for migrated users so new passwords are stored in the native scrypt format. This is a common migration pain point for platforms moving from bcrypt to Better Auth [[source](https://github.com/better-auth/better-auth/issues/4762)]. Improving documentation around hash import requirements and expected formats would help clarify this for future migrations. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/9e6633b5-1ea6-4475-9c6d-6c9ff8d1a87e?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/9e6633b5-1ea6-4475-9c6d-6c9ff8d1a87e?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/9e6633b5-1ea6-4475-9c6d-6c9ff8d1a87e?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/9e6633b5-1ea6-4475-9c6d-6c9ff8d1a87e?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/9e6633b5-1ea6-4475-9c6d-6c9ff8d1a87e?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/9e6633b5-1ea6-4475-9c6d-6c9ff8d1a87e?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/9e6633b5-1ea6-4475-9c6d-6c9ff8d1a87e?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/5016)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10138