[GH-ISSUE #8670] emailOTP client proxy generates incorrect paths for 3 endpoints #19788

Closed
opened 2026-04-15 19:07:59 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @roumel00 on GitHub (Mar 18, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/8670

Is this suited for github?

  • [ x ] Yes, this is suited for github

To Reproduce

  1. Configure emailOTP plugin on the server
  2. Use emailOTPClient() plugin on the client
  3. Call authClient.emailOtp.forgetPasswordEmailOtp({ email })
  4. Client sends POST to /email-otp/forget-password-email-otp — returns 404
  5. Server expects the request at /forget-password/email-otp

Current vs. Expected behavior

The client proxy auto-generates endpoint paths by converting the method chain
from camelCase to kebab-case. 3 emailOTP endpoints have server-defined paths
that don't match this convention, resulting in 404s.

Affected endpoints:

  • forgetPasswordEmailOtp → client sends /email-otp/forget-password-email-otp, server expects /forget-password/email-otp
  • resetPasswordEmailOtp → client sends /email-otp/reset-password-email-otp, server expects /email-otp/reset-password
  • requestPasswordResetEmailOtp → client sends /email-otp/request-password-reset-email-otp, server expects
    /email-otp/request-password-reset

Other emailOTP methods (verifyEmail, sendVerificationOtp, checkVerificationOtp) work correctly because their method names map to matching
server paths.

The root cause is in the client proxy (proxy.mjs) which builds paths via:
"/" + path.map(segment => segment.replace(/[A-Z]/g, letter => -${letter.toLowerCase()})).join("/")

Workaround: use authClient.$fetch('/forget-password/email-otp', { method: 'POST', body: { email } }) with the correct server path.

What version of Better Auth are you using?

1.5.5

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 24.6.0: Mon Jan 19 21:56:28 PST 2026",
    "release": "24.6.0",
    "cpuCount": 12,
    "cpuModel": "Apple M2 Pro",
    "totalMemory": "16.00 GB",
    "freeMemory": "0.27 GB"
  },
  "node": {
    "version": "v22.18.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.5.2"
  },
  "frameworks": [
    {
      "name": "react",
      "version": "^19.2.0"
    }
  ],
  "databases": null,
  "betterAuth": {
    "version": "Unknown",
    "config": null,
    "error": "Couldn't read your auth config. Make sure to default export your auth instance or to export as a variable named auth."
  }
}

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

Client

Auth config (if applicable)

import { betterAuth } from 'better-auth';
  import { emailOTP } from 'better-auth/plugins';

  export const auth = betterAuth({
    emailAndPassword: { enabled: true },
    plugins: [
      emailOTP({
        async sendVerificationOTP({ email, otp, type }) {
          // send email
        },
        sendVerificationOnSignUp: true,
        otpLength: 6,
        expiresIn: 300,
      }),
    ],
  });

Additional context

Tested locally with better-auth@1.5.5 on both client and server.
Using NestJS backend with @thallesp/nestjs-better-auth@2.5.1.

Originally created by @roumel00 on GitHub (Mar 18, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/8670 ### Is this suited for github? - [ x ] Yes, this is suited for github ### To Reproduce 1. Configure `emailOTP` plugin on the server 2. Use `emailOTPClient()` plugin on the client 3. Call `authClient.emailOtp.forgetPasswordEmailOtp({ email })` 4. Client sends POST to `/email-otp/forget-password-email-otp` — returns 404 5. Server expects the request at `/forget-password/email-otp` ### Current vs. Expected behavior The client proxy auto-generates endpoint paths by converting the method chain from camelCase to kebab-case. 3 emailOTP endpoints have server-defined paths that don't match this convention, resulting in 404s. Affected endpoints: - forgetPasswordEmailOtp → client sends `/email-otp/forget-password-email-otp`, server expects `/forget-password/email-otp` - resetPasswordEmailOtp → client sends `/email-otp/reset-password-email-otp`, server expects `/email-otp/reset-password` - requestPasswordResetEmailOtp → client sends `/email-otp/request-password-reset-email-otp`, server expects `/email-otp/request-password-reset` Other emailOTP methods (verifyEmail, sendVerificationOtp, checkVerificationOtp) work correctly because their method names map to matching server paths. The root cause is in the client proxy (proxy.mjs) which builds paths via: `"/" + path.map(segment => segment.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)).join("/")` Workaround: use `authClient.$fetch('/forget-password/email-otp', { method: 'POST', body: { email } })` with the correct server path. ### What version of Better Auth are you using? 1.5.5 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 24.6.0: Mon Jan 19 21:56:28 PST 2026", "release": "24.6.0", "cpuCount": 12, "cpuModel": "Apple M2 Pro", "totalMemory": "16.00 GB", "freeMemory": "0.27 GB" }, "node": { "version": "v22.18.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.5.2" }, "frameworks": [ { "name": "react", "version": "^19.2.0" } ], "databases": null, "betterAuth": { "version": "Unknown", "config": null, "error": "Couldn't read your auth config. Make sure to default export your auth instance or to export as a variable named auth." } } ``` ### Which area(s) are affected? (Select all that apply) Client ### Auth config (if applicable) ```typescript import { betterAuth } from 'better-auth'; import { emailOTP } from 'better-auth/plugins'; export const auth = betterAuth({ emailAndPassword: { enabled: true }, plugins: [ emailOTP({ async sendVerificationOTP({ email, otp, type }) { // send email }, sendVerificationOnSignUp: true, otpLength: 6, expiresIn: 300, }), ], }); ``` ### Additional context Tested locally with better-auth@1.5.5 on both client and server. Using NestJS backend with @thallesp/nestjs-better-auth@2.5.1.
GiteaMirror added the lockedbug labels 2026-04-15 19:07:59 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Mar 18, 2026):

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

Thanks for the detailed bug report! I can confirm this is a valid bug in the codebase.

Root Cause: The emailOTPClient plugin relies entirely on the automatic proxy path generation without defining custom path mappings. The server-side endpoint definitions use non-standard paths that don't match the auto-generated kebab-case conversions.

Additional note: The /forget-password/email-otp endpoint is deprecated and will be removed in the next major version — users should migrate to /email-otp/request-password-reset (which is also affected by this bug).

I didn't find an existing issue tracking this specific path mismatch bug, so this appears to be a new report. The fix would involve adding explicit pathMethods or getActions mappings in the client plugin, similar to how PR #7284 fixed a similar issue for the two-factor plugin.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:4081012455 --> @dosubot[bot] commented on GitHub (Mar 18, 2026): <!-- Greeting --> Hi @roumel00! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> Thanks for the detailed bug report! I can confirm this is a valid bug in the codebase. **Root Cause:** The [emailOTPClient plugin](https://github.com/better-auth/better-auth/blob/8f47015af223e5db9ff406f6ca8247f6b8dcd9fe/packages/better-auth/src/plugins/email-otp/client.ts#L8-L21) relies entirely on the [automatic proxy path generation](https://github.com/better-auth/better-auth/blob/8f47015af223e5db9ff406f6ca8247f6b8dcd9fe/packages/better-auth/src/client/proxy.ts#L70-L76) without defining custom path mappings. The server-side [endpoint definitions](https://github.com/better-auth/better-auth/blob/8f47015af223e5db9ff406f6ca8247f6b8dcd9fe/packages/better-auth/src/plugins/email-otp/routes.ts#L717-L1007) use non-standard paths that don't match the auto-generated kebab-case conversions. **Additional note:** The `/forget-password/email-otp` endpoint is [deprecated](https://github.com/better-auth/better-auth/pull/7645) and will be removed in the next major version — users should migrate to `/email-otp/request-password-reset` (which is also affected by this bug). I didn't find an existing issue tracking this specific path mismatch bug, so this appears to be a new report. The fix would involve adding explicit `pathMethods` or `getActions` mappings in the client plugin, similar to how [PR #7284](https://github.com/better-auth/better-auth/pull/7284) fixed a similar issue for the two-factor plugin. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=brand-link).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/28fa2c80-b9ea-481e-9c51-d80104e369d9?feedback_type=great_response&utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/28fa2c80-b9ea-481e-9c51-d80104e369d9?feedback_type=irrelevant_answer&utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/28fa2c80-b9ea-481e-9c51-d80104e369d9?feedback_type=incorrect_sources&utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/28fa2c80-b9ea-481e-9c51-d80104e369d9?feedback_type=too_verbose&utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/28fa2c80-b9ea-481e-9c51-d80104e369d9?feedback_type=hallucination&utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/28fa2c80-b9ea-481e-9c51-d80104e369d9?feedback_type=bug_report&utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-bug_report) | [Other](https://app.dosu.dev/response-feedback/28fa2c80-b9ea-481e-9c51-d80104e369d9?feedback_type=other&utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=chat-badge)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=Thanks%20for%20the%20detailed%20bug%20report%21%20I%20can%20confirm%20this%20is%20a%20valid%20bug%20in%20the%20codebase.%0A%0A%2A%2ARoot%20Cause%3A%2A%2A%20The%20%5BemailOTPClient%20plugin%5D%28https%3A//github.com/better-auth/better-auth/blob/8f47015af223e5db9ff406f6ca8247f6b8dcd9fe/packages/better-auth/src/plugins/email-otp/client.ts%23L8-L21%29%20relies%20entirely%20on%20the%20%5Bautomatic%20proxy%20path%20generation%5D%28https%3A//github.com/better-auth/better-auth/blob/8f47015af223e5db9ff406f6ca8247f6b8dcd9fe/packages/better-auth/src/client/proxy.ts%23L70-L76%29%20without%20defining%20custom%20path%20mappings.%20The%20server-side%20%5Bendpoint%20definitions%5D%28https%3A//github.com/better-auth/better-auth/blob/8f47015af223e5db9ff406f6ca8247f6b8dcd9fe/packages/better-auth/src/plugins/email-otp/routes.ts%23L717-L1007%29%20use%20non-standard%20paths%20that%20don%27t%20match%20the%20auto-generated%20kebab-case%20conversions.%0A%0A%2A%2AAdditional%20note%3A%2A%2A%20The%20%60/forget-password/email-otp%60%20endpoint%20is%20%5Bdeprecated%5D%28https%3A//github.com/better-auth/better-auth/pull/7645%29%20and%20will%20be%20removed%20in%20the%20next%20major%20version%20%E2%80%94%20users%20should%20migrate%20to%20%60/email-otp/request-password-reset%60%20%28which%20is%20also%20affected%20by%20this%20bug%29.%0A%0AI%20didn%27t%20find%20an%20existing%20issue%20tracking%20this%20specific%20path%20mismatch%20bug%2C%20so%20this%20appears%20to%20be%20a%20new%20report.%20The%20fix%20would%20involve%20adding%20explicit%20%60pathMethods%60%20or%20%60getActions%60%20mappings%20in%20the%20client%20plugin%2C%20similar%20to%20how%20%5BPR%20%237284%5D%28https%3A//github.com/better-auth/better-auth/pull/7284%29%20fixed%20a%20similar%20issue%20for%20the%20two-factor%20plugin.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=join-discord)&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/8670)
Author
Owner

@bytaesu commented on GitHub (Mar 18, 2026):

Hi @roumel00,

It looks like authClient.emailOtp.forgetPasswordEmailOtp is not a supported client method.

For the email OTP password reset flow, the supported client methods are:

  • authClient.emailOtp.requestPasswordReset()
  • authClient.emailOtp.resetPassword()

The old password reset entrypoint is authClient.forgetPassword.emailOtp(), but that one is marked as deprecated and will be removed soon.

So in this case, please use authClient.emailOtp.requestPasswordReset() instead.


Closing this as it doesn't seem to be an actual issue. Feel free to mention me if you have further questions 😁

<!-- gh-comment-id:4084708783 --> @bytaesu commented on GitHub (Mar 18, 2026): Hi @roumel00, It looks like `authClient.emailOtp.forgetPasswordEmailOtp` is not a supported client method. For the email OTP password reset flow, the supported client methods are: - `authClient.emailOtp.requestPasswordReset()` - `authClient.emailOtp.resetPassword()` The old password reset entrypoint is `authClient.forgetPassword.emailOtp()`, but that one is marked as deprecated and will be removed soon. So in this case, please use `authClient.emailOtp.requestPasswordReset()` instead. --- Closing this as it doesn't seem to be an actual issue. Feel free to mention me if you have further questions 😁
Author
Owner

@github-actions[bot] commented on GitHub (Mar 31, 2026):

This issue has been locked as it was closed more than 7 days ago. If you're experiencing a similar problem or you have additional context, please open a new issue and reference this one.

<!-- gh-comment-id:4165914175 --> @github-actions[bot] commented on GitHub (Mar 31, 2026): This issue has been locked as it was closed more than 7 days ago. If you're experiencing a similar problem or you have additional context, please open a new issue and reference this one.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#19788